http://www.uw-madison.lkams.kernel.org/pub/mirrors/fink/distfiles/xscreensaver-4...
[xscreensaver] / hacks / glx / glmatrix.c
diff --git a/hacks/glx/glmatrix.c b/hacks/glx/glmatrix.c
new file mode 100644 (file)
index 0000000..1fa1123
--- /dev/null
@@ -0,0 +1,1021 @@
+/* glmatrix, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ *
+ * GLMatrix -- simulate the text scrolls from the movie "The Matrix".
+ *
+ * This program does a 3D rendering of the dropping characters that
+ * appeared in the title sequences of the movies.  See also `xmatrix'
+ * for a simulation of what the computer monitors actually *in* the
+ * movie did.
+ */
+
+#include <X11/Intrinsic.h>
+
+extern XtAppContext app;
+
+#define PROGCLASS      "GLMatrix"
+#define HACK_INIT      init_matrix
+#define HACK_DRAW      draw_matrix
+#define HACK_RESHAPE   reshape_matrix
+#define HACK_HANDLE_EVENT matrix_handle_event
+#define EVENT_MASK      PointerMotionMask
+#define matrix_opts    xlockmore_opts
+
+#define DEF_SPEED       "1.0"
+#define DEF_DENSITY     "20"
+#define DEF_FOG         "True"
+#define DEF_WAVES       "True"
+#define DEF_ROTATE      "True"
+#define DEF_TEXTURE     "True"
+#define DEF_MODE        "Matrix"
+
+#define DEFAULTS       "*delay:        30000         \n" \
+                       "*showFPS:      False         \n" \
+                       "*wireframe:    False         \n" \
+                       "*mode:       " DEF_MODE    " \n" \
+                       "*speed:      " DEF_SPEED   " \n" \
+                       "*density:    " DEF_DENSITY " \n" \
+                       "*fog:        " DEF_FOG     " \n" \
+                       "*waves:      " DEF_WAVES   " \n" \
+                       "*texture:    " DEF_TEXTURE " \n" \
+                       "*rotate:     " DEF_ROTATE  " \n" \
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef BELLRAND
+#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
+
+#include "xlockmore.h"
+#include "xpm-ximage.h"
+#include <ctype.h>
+
+#include "../images/matrix3.xpm"
+
+#ifdef USE_GL /* whole file */
+
+#include <GL/glu.h>
+
+
+#include "gllist.h"
+
+
+#define CHAR_COLS 16
+#define CHAR_ROWS 13
+static int real_char_rows;
+
+static int matrix_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+                                 192, 193, 194, 195, 196, 197, 198, 199,
+                                 200, 201, 202, 203, 204, 205, 206, 207 };
+static int decimal_encoding[]  = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
+static int hex_encoding[]      = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+                                   33, 34, 35, 36, 37, 38 };
+static int binary_encoding[] = { 16, 17 };
+static int dna_encoding[]    = { 33, 35, 39, 52 };
+#if 0
+static unsigned char char_map[256] = {
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /*   0 */
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /*  16 */
+    0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,  /*  32 */
+   16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,  /*  48 */
+   32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,  /*  64 */
+   48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,  /*  80 */
+   64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  /*  96 */
+   80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,  /* 112 */
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /* 128 */
+    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  /* 144 */
+   96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,  /* 160 */
+  112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,  /* 176 */
+  128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  /* 192 */
+  144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  /* 208 */
+  160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  /* 224 */
+  176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191   /* 240 */
+};
+#endif /* 0 */
+
+#define CURSOR_GLYPH 97
+
+/* #define DEBUG */
+
+#define GRID_SIZE  70     /* width and height of the arena */
+#define GRID_DEPTH 35     /* depth of the arena */
+#define WAVE_SIZE  22     /* periodicity of color (brightness) waves */
+#define SPLASH_RATIO 0.7  /* ratio of GRID_DEPTH where chars hit the screen */
+
+static struct { GLfloat x, y; } nice_views[] = {
+  {  0,     0 },
+  {  0,   -20 },     /* this is a list of viewer rotations that look nice. */
+  {  0,    20 },     /* every now and then we switch to a new one.         */
+  { 25,     0 },     /* (but we only use the first one at start-up.)       */
+  {-25,     0 },
+  { 25,    20 },
+  {-25,    20 },
+  { 25,   -20 },
+  {-25,   -20 },
+
+  { 10,     0 },
+  {-10,     0 },
+  {  0,     0 },  /* prefer these */
+  {  0,     0 },
+  {  0,     0 },
+  {  0,     0 },
+  {  0,     0 },
+};
+
+
+typedef struct {
+  GLfloat x, y, z;        /* position of strip */
+  GLfloat dx, dy, dz;     /* velocity of strip */
+
+  Bool erasing_p;         /* Whether this strip is on its way out. */
+
+  int spinner_glyph;      /* the bottommost glyph -- the feeder */
+  GLfloat spinner_y;      /* where on the strip the bottom glyph is */
+  GLfloat spinner_speed;  /* how fast the bottom glyph drops */
+
+  int glyphs[GRID_SIZE];  /* the other glyphs on the strip, which will be
+                             revealed by the dropping spinner.
+                             0 means no glyph; negative means "spinner".
+                             If non-zero, real value is abs(G)-1. */
+
+  int spin_speed;         /* Rotate all spinners every this-many frames */
+  int spin_tick;          /* frame counter */
+
+  int wave_position;     /* Waves of brightness wash down the strip. */
+  int wave_speed;        /* every this-many frames. */
+  int wave_tick;         /* frame counter. */
+
+} strip;
+
+
+typedef struct {
+  GLXContext *glx_context;
+  Bool button_down_p;
+  GLuint texture;
+  int nstrips;
+  strip *strips;
+  int *glyph_map;
+  int nglyphs;
+  GLfloat tex_char_width, tex_char_height;
+
+  /* auto-tracking direction of view */
+  int last_view, target_view;
+  GLfloat view_x, view_y;
+  int view_steps, view_tick;
+  Bool auto_tracking_p;
+
+} matrix_configuration;
+
+static matrix_configuration *mps = NULL;
+
+static GLfloat brightness_ramp[WAVE_SIZE];
+
+static GLfloat speed;
+static GLfloat density;
+static Bool do_fog;
+static Bool do_waves;
+static Bool do_rotate;
+static Bool do_texture;
+static char *mode_str;
+
+static XrmOptionDescRec opts[] = {
+  { "-speed",       ".speed",     XrmoptionSepArg, 0 },
+  { "-density",     ".density",   XrmoptionSepArg, 0 },
+  { "-mode",        ".mode",      XrmoptionSepArg, 0 },
+  { "-binary",      ".mode",      XrmoptionNoArg, "binary"      },
+  { "-hexadecimal", ".mode",      XrmoptionNoArg, "hexadecimal" },
+  { "-decimal",     ".mode",      XrmoptionNoArg, "decimal"     },
+  { "-dna",         ".mode",      XrmoptionNoArg, "dna"         },
+  { "-fog",         ".fog",       XrmoptionNoArg, "True"  },
+  { "+fog",         ".fog",       XrmoptionNoArg, "False" },
+  { "-waves",       ".waves",     XrmoptionNoArg, "True"  },
+  { "+waves",       ".waves",     XrmoptionNoArg, "False" },
+  { "-rotate",      ".rotate",    XrmoptionNoArg, "True"  },
+  { "+rotate",      ".rotate",    XrmoptionNoArg, "False" },
+  {"-texture",      ".texture",   XrmoptionNoArg, "True"  },
+  {"+texture",      ".texture",   XrmoptionNoArg, "False" },
+};
+
+static argtype vars[] = {
+  {(caddr_t *) &mode_str,   "mode",       "Mode",    DEF_MODE,      t_String},
+  {(caddr_t *) &speed,      "speed",      "Speed",   DEF_SPEED,     t_Float},
+  {(caddr_t *) &density,    "density",    "Density", DEF_DENSITY,   t_Float},
+  {(caddr_t *) &do_fog,     "fog",        "Fog",     DEF_FOG,       t_Bool},
+  {(caddr_t *) &do_waves,   "waves",      "Waves",   DEF_WAVES,     t_Bool},
+  {(caddr_t *) &do_rotate,  "rotate",     "Rotate",  DEF_ROTATE,    t_Bool},
+  {(caddr_t *) &do_texture, "texture",    "Texture", DEF_TEXTURE,   t_Bool},
+};
+
+ModeSpecOpt matrix_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Re-randomize the state of one strip.
+ */
+static void
+reset_strip (ModeInfo *mi, strip *s)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  int i;
+
+  memset (s, 0, sizeof(*s));
+  s->x = (GLfloat) (frand(GRID_SIZE) - (GRID_SIZE/2));
+  s->y = (GLfloat) (GRID_SIZE/2 + BELLRAND(0.5));      /* shift top slightly */
+  s->z = (GLfloat) (GRID_DEPTH * 0.2) - frand (GRID_DEPTH * 0.7);
+  s->spinner_y = 0;
+
+  s->dx = 0;
+/*  s->dx = ((BELLRAND(0.01) - 0.005) * speed); */
+  s->dy = 0;
+  s->dz = (BELLRAND(0.02) * speed);
+
+  s->spinner_speed = (BELLRAND(0.3) * speed);
+
+  s->spin_speed = (int) BELLRAND(2.0 / speed) + 1;
+  s->spin_tick  = 0;
+
+  s->wave_position = 0;
+  s->wave_speed = (int) BELLRAND(3.0 / speed) + 1;
+  s->wave_tick  = 0;
+
+  for (i = 0; i < GRID_SIZE; i++)
+    {
+      int draw_p = (random() % 7);
+      int spin_p = (draw_p && !(random() % 20));
+      int g = (draw_p
+               ? mp->glyph_map[(random() % mp->nglyphs)] + 1
+               : 0);
+      if (spin_p) g = -g;
+      s->glyphs[i] = g;
+    }
+  s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1);
+}
+
+
+/* Animate the strip one step.  Reset if it has reached the bottom.
+ */
+static void
+tick_strip (ModeInfo *mi, strip *s)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  int i;
+
+  if (mp->button_down_p)
+    return;
+
+  s->x += s->dx;
+  s->y += s->dy;
+  s->z += s->dz;
+
+  if (s->z > GRID_DEPTH * SPLASH_RATIO)  /* splashed into screen */
+    {
+      reset_strip (mi, s);
+      return;
+    }
+
+  s->spinner_y += s->spinner_speed;
+  if (s->spinner_y >= GRID_SIZE)
+    {
+      if (s->erasing_p)
+        {
+          reset_strip (mi, s);
+          return;
+        }
+      else
+        {
+          s->erasing_p = True;
+          s->spinner_y = 0;
+          s->spinner_speed /= 2;  /* erase it slower than we drew it */
+        }
+    }
+
+  /* Spin the spinners. */
+  s->spin_tick++;
+  if (s->spin_tick > s->spin_speed)
+    {
+      s->spin_tick = 0;
+      s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1);
+      for (i = 0; i < GRID_SIZE; i++)
+        if (s->glyphs[i] < 0)
+          {
+            s->glyphs[i] = -(mp->glyph_map[(random() % mp->nglyphs)] + 1);
+            if (! (random() % 800))  /* sometimes they stop spinning */
+              s->glyphs[i] = -s->glyphs[i];
+          }
+    }
+
+  /* Move the color (brightness) wave. */
+  s->wave_tick++;
+  if (s->wave_tick > s->wave_speed)
+    {
+      s->wave_tick = 0;
+      s->wave_position++;
+      if (s->wave_position >= WAVE_SIZE)
+        s->wave_position = 0;
+    }
+}
+
+
+/* Draw a single character at the given position and brightness.
+ */
+static void
+draw_glyph (ModeInfo *mi, int glyph,
+            GLfloat x, GLfloat y, GLfloat z,
+            GLfloat brightness)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  int wire = MI_IS_WIREFRAME(mi);
+  GLfloat w = mp->tex_char_width;
+  GLfloat h = mp->tex_char_height;
+  GLfloat cx, cy;
+  GLfloat S = 1;
+  Bool spinner_p = (glyph < 0);
+
+  if (glyph == 0) abort();
+  if (glyph < 0) glyph = -glyph;
+
+  if (spinner_p)
+    brightness *= 1.5;
+
+  if (!do_texture)
+    {
+      S  = 0.8;
+      x += 0.1;
+      y += 0.1;
+    }
+  else
+    {
+      int ccx = ((glyph - 1) % CHAR_COLS);
+      int ccy = ((glyph - 1) / CHAR_COLS);
+
+      cx = ccx * w;
+      cy = (real_char_rows - ccy - 1) * h;
+
+      if (do_fog)
+        {
+          GLfloat depth;
+          depth = (z / GRID_DEPTH) + 0.5;  /* z ratio from back/front      */
+          depth = 0.2 + (depth * 0.8);     /* scale to range [0.2 - 1.0]   */
+          brightness *= depth;             /* so no row goes all black.    */
+        }
+    }
+
+  {
+    GLfloat r, g, b, a = 1;
+    if (!do_texture && !spinner_p)
+      r = b = 0, g = brightness;
+    else
+      r = g = b = brightness;
+
+    /* If the glyph is very close to the screen (meaning it is very large,
+       and is about to splash into the screen and vanish) then start fading
+       it out, proportional to how close to the glass it is.
+    */
+    if (z > GRID_DEPTH/2)
+      {
+        GLfloat ratio = ((z - GRID_DEPTH/2) /
+                         ((GRID_DEPTH * SPLASH_RATIO) - GRID_DEPTH/2));
+        int i = ratio * WAVE_SIZE;
+
+        if (i < 0) i = 0;
+        else if (i >= WAVE_SIZE) i = WAVE_SIZE-1; 
+
+        a = brightness_ramp[i];
+#if 1
+        /* I don't understand this -- if I change the alpha on the color of
+           the quad, I'd expect that to make the quad more transparent.
+           But instead, it seems to be making the transparent parts of the
+           texture on the quad be *less* transparent!  So as we fade out,
+           we fade towards a completely solid rectangle.  WTF?
+
+           So, for now, instead of changing the alpha, just make the colors
+           be darker.  This isn't quite right (it causes a large dark glyph
+           to occlude the brighter glyphs behind it) but it's close...
+         */
+        r *= a;
+        g *= a;
+        b *= a;
+        a = 1;
+#endif
+      }
+
+    glColor4f (r,g,b,a);
+  }
+
+  glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+  glNormal3f (0, 0, 1);
+  glTexCoord2f (cx,   cy);   glVertex3f (x,   y,   z);
+  glTexCoord2f (cx+w, cy);   glVertex3f (x+S, y,   z);
+  glTexCoord2f (cx+w, cy+h); glVertex3f (x+S, y+S, z);
+  glTexCoord2f (cx,   cy+h); glVertex3f (x,   y+S, z);
+  glEnd ();
+
+  if (wire && spinner_p)
+    {
+      glBegin (GL_LINES);
+      glVertex3f (x,   y,   z);
+      glVertex3f (x+S, y+S, z);
+      glVertex3f (x,   y+S, z);
+      glVertex3f (x+S, y,   z);
+      glEnd();
+    }
+
+  mi->polygon_count++;
+}
+
+
+/* Draw all the visible glyphs in the strip.
+ */
+static void
+draw_strip (ModeInfo *mi, strip *s)
+{
+  int i;
+  for (i = 0; i < GRID_SIZE; i++)
+    {
+      int g = s->glyphs[i];
+      Bool below_p = (s->spinner_y >= i);
+
+      if (s->erasing_p)
+        below_p = !below_p;
+
+      if (g && below_p)       /* don't draw cells below the spinner */
+        {
+          GLfloat brightness;
+          if (!do_waves)
+            brightness = 1.0;
+          else
+            {
+              int j = WAVE_SIZE - ((i + (GRID_SIZE - s->wave_position))
+                                   % WAVE_SIZE);
+              brightness = brightness_ramp[j];
+            }
+
+          draw_glyph (mi, g, s->x, s->y - i, s->z, brightness);
+        }
+    }
+
+  if (!s->erasing_p)
+    draw_glyph (mi, s->spinner_glyph, s->x, s->y - s->spinner_y, s->z, 1.0);
+}
+
+
+/* qsort comparator for sorting strips by z position */
+static int
+cmp_strips (const void *aa, const void *bb)
+{
+  const strip *a = *(strip **) aa;
+  const strip *b = *(strip **) bb;
+  return ((int) (a->z * 10000) -
+          (int) (b->z * 10000));
+}
+
+
+/* Auto-tracking
+ */
+
+static void
+auto_track_init (ModeInfo *mi)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  mp->last_view = 0;
+  mp->target_view = 0;
+  mp->view_x = nice_views[mp->last_view].x;
+  mp->view_y = nice_views[mp->last_view].y;
+  mp->view_steps = 100;
+  mp->view_tick = 0;
+  mp->auto_tracking_p = False;
+}
+
+
+static void
+auto_track (ModeInfo *mi)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+
+  if (! do_rotate)
+    return;
+  if (mp->button_down_p)
+    return;
+
+  /* if we're not moving, maybe start moving.  Otherwise, do nothing. */
+  if (! mp->auto_tracking_p)
+    {
+      static int tick = 0;
+      if (++tick < 20/speed) return;
+      tick = 0;
+      if (! (random() % 20))
+        mp->auto_tracking_p = True;
+      else
+        return;
+    }
+
+
+  {
+    GLfloat ox = nice_views[mp->last_view].x;
+    GLfloat oy = nice_views[mp->last_view].y;
+    GLfloat tx = nice_views[mp->target_view].x;
+    GLfloat ty = nice_views[mp->target_view].y;
+
+    /* move from A to B with sinusoidal deltas, so that it doesn't jerk
+       to a stop. */
+    GLfloat th = sin ((M_PI / 2) * (double) mp->view_tick / mp->view_steps);
+
+    mp->view_x = (ox + ((tx - ox) * th));
+    mp->view_y = (oy + ((ty - oy) * th));
+    mp->view_tick++;
+
+  if (mp->view_tick >= mp->view_steps)
+    {
+      mp->view_tick = 0;
+      mp->view_steps = (350.0 / speed);
+      mp->last_view = mp->target_view;
+      mp->target_view = (random() % (countof(nice_views) - 1)) + 1;
+      mp->auto_tracking_p = False;
+    }
+  }
+}
+
+
+/* Window management, etc
+ */
+void
+reshape_matrix (ModeInfo *mi, int width, int height)
+{
+  GLfloat h = (GLfloat) height / (GLfloat) width;
+
+  glViewport (0, 0, (GLint) width, (GLint) height);
+
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective (80.0, 1/h, 1.0, 100);
+
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  gluLookAt( 0.0, 0.0, 25.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
+
+  glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+Bool
+matrix_handle_event (ModeInfo *mi, XEvent *event)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+
+  if (event->xany.type == ButtonPress &&
+      event->xbutton.button & Button1)
+    {
+      mp->button_down_p = True;
+      return True;
+    }
+  else if (event->xany.type == ButtonRelease &&
+           event->xbutton.button & Button1)
+    {
+      mp->button_down_p = False;
+      return True;
+    }
+
+  return False;
+}
+
+
+static Bool
+bigendian (void)
+{
+  union { int i; char c[sizeof(int)]; } u;
+  u.i = 1;
+  return !u.c[0];
+}
+
+
+/* The image with the characters in it is 512x598, meaning that it needs to
+   be copied into a 512x1024 texture.  But some machines can't handle textures
+   that large...  And it turns out that we aren't using most of the characters
+   in that image anyway, since this program doesn't do anything that makes use
+   of the full range of Latin1 characters.  So... this function tosses out the
+   last 32 of the Latin1 characters, resulting in a 512x506 image, which we
+   can then stuff in a 512x512 texture.  Voila.
+
+   If this hack ever grows into something that displays full Latin1 text,
+   well then, Something Else Will Need To Be Done.
+ */
+static void
+spank_image (XImage *xi)
+{
+  int ch = xi->height / CHAR_ROWS;
+  int cut = 2;
+  unsigned char *bits = (unsigned char *) xi->data;
+  unsigned char *from, *to, *s, *end;
+  int L = xi->bytes_per_line * ch;
+  int i;
+
+  /* Copy row 12 into 10 (which really means, copy 2 into 0,
+     since texture data is upside down.).
+  */
+  to   = bits + (L * cut);
+  from = bits;
+  end  = from + L;
+  s    = from;
+  while (s < end)
+    *to++ = *s++;
+
+  /* Then, pull all the bits down by 2 rows.
+   */
+  to   = bits;
+  from = bits + (L * cut);
+  end  = bits + (L * CHAR_ROWS);
+  s    = from;
+  while (s < end)
+    *to++ = *s++;
+
+  /* And clear out the rest, for good measure.
+   */
+  from = bits + (L * (CHAR_ROWS - cut));
+  end  = bits + (L * CHAR_ROWS);
+  s    = from;
+  while (s < end)
+    *s++ = 0;
+
+  xi->height -= (cut * ch);
+  real_char_rows -= cut;
+
+  /* Finally, pull the map indexes back to match the new bits.
+   */
+  for (i = 0; i < countof(matrix_encoding); i++)
+    if (matrix_encoding[i] > (CHAR_COLS * (CHAR_ROWS - cut)))
+      matrix_encoding[i] -= (cut * CHAR_COLS);
+}
+
+
+static void
+load_textures (ModeInfo *mi, Bool flip_p)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  XImage *xi;
+  int x, y;
+  int cw, ch;
+  int orig_w, orig_h;
+
+  /* The Matrix XPM is 512x598 -- but GL texture sizes must be powers of 2.
+     So we waste some padding rows to round up.
+   */
+  xi = xpm_to_ximage (mi->dpy, mi->xgwa.visual, mi->xgwa.colormap,
+                      matrix3_xpm);
+  orig_w = xi->width;
+  orig_h = xi->height;
+  real_char_rows = CHAR_ROWS;
+  spank_image (xi);
+
+  if (xi->height != 512 && xi->height != 1024)
+    {
+      xi->height = (xi->height < 512 ? 512 : 1024);
+      xi->data = realloc (xi->data, xi->height * xi->bytes_per_line);
+      if (!xi->data)
+        {
+          fprintf(stderr, "%s: out of memory\n", progname);
+          exit(1);
+        }
+    }
+
+  if (xi->width != 512) abort();
+  if (xi->height != 512 && xi->height != 1024) abort();
+
+  /* char size in pixels */
+  cw = orig_w / CHAR_COLS;
+  ch = orig_h / CHAR_ROWS;
+
+  /* char size in ratio of final (padded) texture size */
+  mp->tex_char_width  = (GLfloat) cw / xi->width;
+  mp->tex_char_height = (GLfloat) ch / xi->height;
+
+  /* Flip each character's bits horizontally -- we could also just do this
+     by reversing the texture coordinates on the quads, but on some systems
+     that slows things down a lot.
+   */
+  if (flip_p)
+    {
+      int xx, col;
+      unsigned long buf[100];
+      for (y = 0; y < xi->height; y++)
+        for (col = 0, xx = 0; col < CHAR_COLS; col++, xx += cw)
+          {
+            for (x = 0; x < cw; x++)
+              buf[x] = XGetPixel (xi, xx+x, y);
+            for (x = 0; x < cw; x++)
+              XPutPixel (xi, xx+x, y, buf[cw-x-1]);
+          }
+    }
+
+  /* The pixmap is a color image with no transparency.  Set the texture's
+     alpha to be the green channel, and set the green channel to be 100%.
+   */
+  {
+    int rpos, gpos, bpos, apos;  /* bitfield positions */
+    if (bigendian())
+      rpos = 24, gpos = 16, bpos =  8, apos =  0;
+    else
+      rpos =  0, gpos =  8, bpos = 16, apos = 24;
+
+    for (y = 0; y < xi->height; y++)
+      for (x = 0; x < xi->width; x++)
+        {
+          unsigned long p = XGetPixel (xi, x, y);
+          unsigned char r = (p >> rpos) & 0xFF;
+          unsigned char g = (p >> gpos) & 0xFF;
+          unsigned char b = (p >> bpos) & 0xFF;
+          unsigned char a = ~g;
+          g = 0xFF;
+          p = (r << rpos) | (g << gpos) | (b << bpos) | (a << apos);
+          XPutPixel (xi, x, y, p);
+        }
+  }
+
+  /* Now load the texture into GL.
+   */
+  clear_gl_error();
+  glGenTextures (1, &mp->texture);
+
+  glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
+  glPixelStorei (GL_UNPACK_ROW_LENGTH, xi->width);
+  glBindTexture (GL_TEXTURE_2D, mp->texture);
+  check_gl_error ("texture init");
+  glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, xi->width, xi->height, 0, GL_RGBA,
+                GL_UNSIGNED_INT_8_8_8_8_REV, xi->data);
+  {
+    char buf[255];
+    sprintf (buf, "creating %dx%d texture:", xi->width, xi->height);
+    check_gl_error (buf);
+  }
+
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+  /* I'd expect CLAMP to be the thing to do here, but oddly, we get a
+     faint solid green border around the texture if it is *not* REPEAT!
+  */
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+  glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+  glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+  check_gl_error ("texture param");
+
+  xi->data = 0;  /* don't free the texture data */
+  XDestroyImage (xi);
+}
+
+
+void 
+init_matrix (ModeInfo *mi)
+{
+  matrix_configuration *mp;
+  int wire = MI_IS_WIREFRAME(mi);
+  Bool flip_p = 0;
+  int i;
+
+  if (wire)
+    do_texture = False;
+
+  if (!mps) {
+    mps = (matrix_configuration *)
+      calloc (MI_NUM_SCREENS(mi), sizeof (matrix_configuration));
+    if (!mps) {
+      fprintf(stderr, "%s: out of memory\n", progname);
+      exit(1);
+    }
+  }
+
+  mp = &mps[MI_SCREEN(mi)];
+  mp->glx_context = init_GL(mi);
+
+  if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix"))
+    {
+      flip_p = 1;
+      mp->glyph_map = matrix_encoding;
+      mp->nglyphs   = countof(matrix_encoding);
+    }
+  else if (!strcasecmp (mode_str, "dna"))
+    {
+      flip_p = 0;
+      mp->glyph_map = dna_encoding;
+      mp->nglyphs   = countof(dna_encoding);
+    }
+  else if (!strcasecmp (mode_str, "bin") ||
+           !strcasecmp (mode_str, "binary"))
+    {
+      flip_p = 0;
+      mp->glyph_map = binary_encoding;
+      mp->nglyphs   = countof(binary_encoding);
+    }
+  else if (!strcasecmp (mode_str, "hex") ||
+           !strcasecmp (mode_str, "hexadecimal"))
+    {
+      flip_p = 0;
+      mp->glyph_map = hex_encoding;
+      mp->nglyphs   = countof(hex_encoding);
+    }
+  else if (!strcasecmp (mode_str, "dec") ||
+           !strcasecmp (mode_str, "decimal"))
+    {
+      flip_p = 0;
+      mp->glyph_map = decimal_encoding;
+      mp->nglyphs   = countof(decimal_encoding);
+    }
+  else
+    {
+      fprintf (stderr,
+           "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n",
+               progname, mode_str);
+      exit (1);
+    }
+
+  reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+  glShadeModel(GL_SMOOTH);
+
+  glDisable(GL_DEPTH_TEST);
+  glDisable(GL_CULL_FACE);
+  glEnable(GL_NORMALIZE);
+
+  if (do_texture)
+    {
+      load_textures (mi, flip_p);
+      glEnable(GL_TEXTURE_2D);
+      glEnable(GL_BLEND);
+      glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
+    }
+
+  /* to scale coverage-percent to strips, this number looks about right... */
+  mp->nstrips = (int) (density * 2.2);
+  if      (mp->nstrips < 1)    mp->nstrips = 1;
+  else if (mp->nstrips > 2000) mp->nstrips = 2000;
+
+
+  mp->strips = calloc (mp->nstrips, sizeof(strip));
+  for (i = 0; i < mp->nstrips; i++)
+    {
+      strip *s = &mp->strips[i];
+      reset_strip (mi, s);
+
+      /* If we start all strips from zero at once, then the first few seconds
+         of the animation are much denser than normal.  So instead, set all
+         the initial strips to erase-mode with random starting positions.
+         As these die off at random speeds and are re-created, we'll get a
+         more consistent density. */
+      s->erasing_p = True;
+      s->spinner_y = frand(GRID_SIZE);
+      memset (s->glyphs, 0, sizeof(s->glyphs));  /* no visible glyphs */
+    }
+
+  /* Compute the brightness ramp.
+   */
+  for (i = 0; i < WAVE_SIZE; i++)
+    {
+      GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1));
+      j *= (M_PI / 2);       /* j ranges from 0.0 - PI/2  */
+      j = sin (j);           /* j ranges from 0.0 - 1.0   */
+      j = 0.2 + (j * 0.8);   /* j ranges from 0.2 - 1.0   */
+      brightness_ramp[i] = j;
+      /* printf("%2d %8.2f\n", i, j); */
+    }
+
+
+  auto_track_init (mi);
+}
+
+
+#ifdef DEBUG
+
+static void
+draw_grid (ModeInfo *mi)
+{
+  if (!MI_IS_WIREFRAME(mi))
+    {
+      glDisable(GL_TEXTURE_2D);
+      glDisable(GL_BLEND);
+    }
+  glPushMatrix();
+  glColor3f(1, 1, 1);
+  glBegin(GL_LINES);
+  glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0);
+  glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0);
+  glEnd();
+  glBegin(GL_LINE_LOOP);
+  glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0);
+  glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, 0);
+  glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, 0);
+  glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0);
+  glEnd();
+  glBegin(GL_LINE_LOOP);
+  glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f(-GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
+  glEnd();
+  glBegin(GL_LINE_LOOP);
+  glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
+  glEnd();
+  glBegin(GL_LINES);
+  glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, -GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
+  glVertex3f( GRID_SIZE/2,  GRID_SIZE/2,  GRID_DEPTH/2);
+  glEnd();
+  glPopMatrix();
+  if (!MI_IS_WIREFRAME(mi))
+    {
+      glEnable(GL_TEXTURE_2D);
+      glEnable(GL_BLEND);
+    }
+}
+#endif /* DEBUG */
+
+
+void
+draw_matrix (ModeInfo *mi)
+{
+  matrix_configuration *mp = &mps[MI_SCREEN(mi)];
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+  int i;
+
+  if (!mp->glx_context)
+    return;
+
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushMatrix ();
+
+  if (do_rotate)
+    {
+      glRotatef (mp->view_x, 1, 0, 0);
+      glRotatef (mp->view_y, 0, 1, 0);
+    }
+
+#ifdef DEBUG
+# if 0
+  glScalef(0.5, 0.5, 0.5);
+# endif
+# if 0
+  glRotatef(-30, 0, 1, 0); 
+# endif
+  draw_grid (mi);
+#endif
+
+  mi->polygon_count = 0;
+
+  /* Render (and tick) each strip, starting at the back
+     (draw the ones farthest from the camera first, to make
+     the alpha transparency work out right.)
+   */
+  {
+    strip **sorted = malloc (mp->nstrips * sizeof(*sorted));
+    for (i = 0; i < mp->nstrips; i++)
+      sorted[i] = &mp->strips[i];
+    qsort (sorted, i, sizeof(*sorted), cmp_strips);
+
+    for (i = 0; i < mp->nstrips; i++)
+      {
+        strip *s = sorted[i];
+        tick_strip (mi, s);
+        draw_strip (mi, s);
+      }
+    free (sorted);
+  }
+
+  auto_track (mi);
+
+#if 0
+  glBegin(GL_QUADS);
+  glColor3f(1,1,1);
+  glTexCoord2f (0,0);  glVertex3f(-15,-15,0);
+  glTexCoord2f (0,1);  glVertex3f(-15,15,0);
+  glTexCoord2f (1,1);  glVertex3f(15,15,0);
+  glTexCoord2f (1,0);  glVertex3f(15,-15,0);
+  glEnd();
+#endif
+
+  glPopMatrix ();
+
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
+
+  glXSwapBuffers(dpy, window);
+}
+
+#endif /* USE_GL */