47069eb36422918159dfff99904cd8d830731043
[xscreensaver] / hacks / glx / glmatrix.c
1 /* glmatrix, Copyright (c) 2003, 2004 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * GLMatrix -- simulate the text scrolls from the movie "The Matrix".
12  *
13  * This program does a 3D rendering of the dropping characters that
14  * appeared in the title sequences of the movies.  See also `xmatrix'
15  * for a simulation of what the computer monitors actually *in* the
16  * movie did.
17  */
18
19 #define DEFAULTS        "*delay:        30000         \n" \
20                         "*showFPS:      False         \n" \
21                         "*wireframe:    False         \n" \
22
23 # define refresh_matrix 0
24 # define release_matrix 0
25 #undef countof
26 #define countof(x) (sizeof((x))/sizeof((*x)))
27
28 #undef BELLRAND
29 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
30
31 #include "xlockmore.h"
32 #include "xpm-ximage.h"
33
34 #ifdef __GNUC__
35   __extension__  /* don't warn about "string length is greater than the length
36                     ISO C89 compilers are required to support" when including
37                     the following XPM file... */
38 #endif
39 #include "../images/matrix3.xpm"
40
41 #ifdef USE_GL /* whole file */
42
43
44 #define DEF_SPEED       "1.0"
45 #define DEF_DENSITY     "20"
46 #define DEF_CLOCK       "False"
47 #define DEF_FOG         "True"
48 #define DEF_WAVES       "True"
49 #define DEF_ROTATE      "True"
50 #define DEF_TEXTURE     "True"
51 #define DEF_MODE        "Matrix"
52 #define DEF_TIMEFMT     " %l%M%p "
53
54
55 #define CHAR_COLS 16
56 #define CHAR_ROWS 13
57
58 static const int matrix_encoding[] = {
59     16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
60 # if 0
61     192, 193, 194, 195, 196, 197, 198, 199,
62     200, 201, 202, 203, 204, 205, 206, 207
63 # else
64     160, 161, 162, 163, 164, 165, 166, 167,
65     168, 169, 170, 171, 172, 173, 174, 175
66 # endif
67   };
68 static const int decimal_encoding[]  = {
69   16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
70 static const int hex_encoding[] = {
71   16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38 };
72 static const int binary_encoding[] = { 16, 17 };
73 static const int dna_encoding[]    = { 33, 35, 39, 52 };
74
75 static const unsigned char char_map[256] = {
76    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /*   0 */
77    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /*  16 */
78     0,  1,  2, 96,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,  /*  32 */
79    16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,  /*  48 */
80    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,  /*  64 */
81    48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,  /*  80 */
82    64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,  /*  96 */
83    80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,  /* 112 */
84    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /* 128 */
85    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /* 144 */
86    96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,  /* 160 */
87   112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,  /* 176 */
88   128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,  /* 192 */
89   144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,  /* 208 */
90 #if 0
91   160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,  /* 224 */
92   176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191   /* 240 */
93 #else /* see spank_image() */
94    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /* 224 */
95    96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,  /* 240 */
96 #endif
97 };
98
99 #define CURSOR_GLYPH 97
100
101 /* #define DEBUG */
102
103 #define GRID_SIZE  70     /* width and height of the arena */
104 #define GRID_DEPTH 35     /* depth of the arena */
105 #define WAVE_SIZE  22     /* periodicity of color (brightness) waves */
106 #define SPLASH_RATIO 0.7  /* ratio of GRID_DEPTH where chars hit the screen */
107
108 static const struct { GLfloat x, y; } nice_views[] = {
109   {  0,     0 },
110   {  0,   -20 },     /* this is a list of viewer rotations that look nice. */
111   {  0,    20 },     /* every now and then we switch to a new one.         */
112   { 25,     0 },     /* (but we only use the first one at start-up.)       */
113   {-25,     0 },
114   { 25,    20 },
115   {-25,    20 },
116   { 25,   -20 },
117   {-25,   -20 },
118
119   { 10,     0 },
120   {-10,     0 },
121   {  0,     0 },  /* prefer these */
122   {  0,     0 },
123   {  0,     0 },
124   {  0,     0 },
125   {  0,     0 },
126 };
127
128
129 typedef struct {
130   GLfloat x, y, z;        /* position of strip */
131   GLfloat dx, dy, dz;     /* velocity of strip */
132
133   Bool erasing_p;         /* Whether this strip is on its way out. */
134
135   int spinner_glyph;      /* the bottommost glyph -- the feeder */
136   GLfloat spinner_y;      /* where on the strip the bottom glyph is */
137   GLfloat spinner_speed;  /* how fast the bottom glyph drops */
138
139   int glyphs[GRID_SIZE];  /* the other glyphs on the strip, which will be
140                              revealed by the dropping spinner.
141                              0 means no glyph; negative means "spinner".
142                              If non-zero, real value is abs(G)-1. */
143
144   Bool highlight[GRID_SIZE];
145                           /* some glyphs may be highlighted */
146   
147   int spin_speed;         /* Rotate all spinners every this-many frames */
148   int spin_tick;          /* frame counter */
149
150   int wave_position;      /* Waves of brightness wash down the strip. */
151   int wave_speed;         /* every this-many frames. */
152   int wave_tick;          /* frame counter. */
153
154 } strip;
155
156
157 typedef struct {
158   GLXContext *glx_context;
159   Bool button_down_p;
160   GLuint texture;
161   int nstrips;
162   strip *strips;
163   const int *glyph_map;
164   int nglyphs;
165   GLfloat tex_char_width, tex_char_height;
166
167   /* auto-tracking direction of view */
168   int last_view, target_view;
169   GLfloat view_x, view_y;
170   int view_steps, view_tick;
171   Bool auto_tracking_p;
172   int track_tick;
173
174   int real_char_rows;
175   GLfloat brightness_ramp[WAVE_SIZE];
176
177 } matrix_configuration;
178
179 static matrix_configuration *mps = NULL;
180
181 static GLfloat speed;
182 static GLfloat density;
183 static Bool do_clock;
184 static char *timefmt;
185 static Bool do_fog;
186 static Bool do_waves;
187 static Bool do_rotate;
188 static Bool do_texture;
189 static char *mode_str;
190
191 static XrmOptionDescRec opts[] = {
192   { "-speed",       ".speed",     XrmoptionSepArg, 0 },
193   { "-density",     ".density",   XrmoptionSepArg, 0 },
194   { "-mode",        ".mode",      XrmoptionSepArg, 0 },
195   { "-binary",      ".mode",      XrmoptionNoArg, "binary"      },
196   { "-hexadecimal", ".mode",      XrmoptionNoArg, "hexadecimal" },
197   { "-decimal",     ".mode",      XrmoptionNoArg, "decimal"     },
198   { "-dna",         ".mode",      XrmoptionNoArg, "dna"         },
199   { "-clock",       ".clock",     XrmoptionNoArg, "True"  },
200   { "+clock",       ".clock",     XrmoptionNoArg, "False" },
201   { "-timefmt",     ".timefmt",   XrmoptionSepArg, 0  },
202   { "-fog",         ".fog",       XrmoptionNoArg, "True"  },
203   { "+fog",         ".fog",       XrmoptionNoArg, "False" },
204   { "-waves",       ".waves",     XrmoptionNoArg, "True"  },
205   { "+waves",       ".waves",     XrmoptionNoArg, "False" },
206   { "-rotate",      ".rotate",    XrmoptionNoArg, "True"  },
207   { "+rotate",      ".rotate",    XrmoptionNoArg, "False" },
208   {"-texture",      ".texture",   XrmoptionNoArg, "True"  },
209   {"+texture",      ".texture",   XrmoptionNoArg, "False" },
210 };
211
212 static argtype vars[] = {
213   {&mode_str,   "mode",       "Mode",    DEF_MODE,      t_String},
214   {&speed,      "speed",      "Speed",   DEF_SPEED,     t_Float},
215   {&density,    "density",    "Density", DEF_DENSITY,   t_Float},
216   {&do_clock,   "clock",      "Clock",   DEF_CLOCK,     t_Bool},
217   {&timefmt,    "timefmt",    "Timefmt", DEF_TIMEFMT,   t_String},
218   {&do_fog,     "fog",        "Fog",     DEF_FOG,       t_Bool},
219   {&do_waves,   "waves",      "Waves",   DEF_WAVES,     t_Bool},
220   {&do_rotate,  "rotate",     "Rotate",  DEF_ROTATE,    t_Bool},
221   {&do_texture, "texture",    "Texture", DEF_TEXTURE,   t_Bool},
222 };
223
224 ENTRYPOINT ModeSpecOpt matrix_opts = {countof(opts), opts, countof(vars), vars, NULL};
225
226
227 /* Re-randomize the state of one strip.
228  */
229 static void
230 reset_strip (ModeInfo *mi, strip *s)
231 {
232   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
233   int i;
234   Bool time_displayed_p = False;  /* never display time twice in one strip */
235
236   memset (s, 0, sizeof(*s));
237   s->x = (GLfloat) (frand(GRID_SIZE) - (GRID_SIZE/2));
238   s->y = (GLfloat) (GRID_SIZE/2 + BELLRAND(0.5));      /* shift top slightly */
239   s->z = (GLfloat) (GRID_DEPTH * 0.2) - frand (GRID_DEPTH * 0.7);
240   s->spinner_y = 0;
241
242   s->dx = 0;
243 /*  s->dx = ((BELLRAND(0.01) - 0.005) * speed); */
244   s->dy = 0;
245   s->dz = (BELLRAND(0.02) * speed);
246
247   s->spinner_speed = (BELLRAND(0.3) * speed);
248
249   s->spin_speed = (int) BELLRAND(2.0 / speed) + 1;
250   s->spin_tick  = 0;
251
252   s->wave_position = 0;
253   s->wave_speed = (int) BELLRAND(3.0 / speed) + 1;
254   s->wave_tick  = 0;
255
256   for (i = 0; i < GRID_SIZE; i++)
257     if (do_clock &&
258         !time_displayed_p &&
259         (i < GRID_SIZE-5) &&   /* display approx. once per 5 strips */
260         !(random() % (GRID_SIZE-5)*5))
261       {
262         int j;
263         char text[80];
264         time_t now = time ((time_t *) 0);
265         struct tm *tm = localtime (&now);
266         strftime (text, sizeof(text)-1, timefmt, tm);
267
268         /* render time into the strip */
269         for (j = 0; j < strlen(text) && i < GRID_SIZE; j++, i++)
270           {
271             s->glyphs[i] = char_map [((unsigned char *) text)[j]] + 1;
272             s->highlight[i] = True;
273           }
274
275         time_displayed_p = True;        
276       }
277     else
278       {
279         int draw_p = (random() % 7);
280         int spin_p = (draw_p && !(random() % 20));
281         int g = (draw_p
282                  ? mp->glyph_map[(random() % mp->nglyphs)] + 1
283                  : 0);
284         if (spin_p) g = -g;
285         s->glyphs[i] = g;
286         s->highlight[i] = False;
287       }
288
289   s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1);
290 }
291
292
293 /* Animate the strip one step.  Reset if it has reached the bottom.
294  */
295 static void
296 tick_strip (ModeInfo *mi, strip *s)
297 {
298   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
299   int i;
300
301   if (mp->button_down_p)
302     return;
303
304   s->x += s->dx;
305   s->y += s->dy;
306   s->z += s->dz;
307
308   if (s->z > GRID_DEPTH * SPLASH_RATIO)  /* splashed into screen */
309     {
310       reset_strip (mi, s);
311       return;
312     }
313
314   s->spinner_y += s->spinner_speed;
315   if (s->spinner_y >= GRID_SIZE)
316     {
317       if (s->erasing_p)
318         {
319           reset_strip (mi, s);
320           return;
321         }
322       else
323         {
324           s->erasing_p = True;
325           s->spinner_y = 0;
326           s->spinner_speed /= 2;  /* erase it slower than we drew it */
327         }
328     }
329
330   /* Spin the spinners. */
331   s->spin_tick++;
332   if (s->spin_tick > s->spin_speed)
333     {
334       s->spin_tick = 0;
335       s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1);
336       for (i = 0; i < GRID_SIZE; i++)
337         if (s->glyphs[i] < 0)
338           {
339             s->glyphs[i] = -(mp->glyph_map[(random() % mp->nglyphs)] + 1);
340             if (! (random() % 800))  /* sometimes they stop spinning */
341               s->glyphs[i] = -s->glyphs[i];
342           }
343     }
344
345   /* Move the color (brightness) wave. */
346   s->wave_tick++;
347   if (s->wave_tick > s->wave_speed)
348     {
349       s->wave_tick = 0;
350       s->wave_position++;
351       if (s->wave_position >= WAVE_SIZE)
352         s->wave_position = 0;
353     }
354 }
355
356
357 /* Draw a single character at the given position and brightness.
358  */
359 static void
360 draw_glyph (ModeInfo *mi, int glyph, Bool highlight,
361             GLfloat x, GLfloat y, GLfloat z,
362             GLfloat brightness)
363 {
364   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
365   int wire = MI_IS_WIREFRAME(mi);
366   GLfloat w = mp->tex_char_width;
367   GLfloat h = mp->tex_char_height;
368   GLfloat cx = 0, cy = 0;
369   GLfloat S = 1;
370   Bool spinner_p = (glyph < 0);
371
372   if (glyph == 0) abort();
373   if (glyph < 0) glyph = -glyph;
374
375   if (spinner_p)
376     brightness *= 1.5;
377
378   if (!do_texture)
379     {
380       S  = 0.8;
381       x += 0.1;
382       y += 0.1;
383     }
384   else
385     {
386       int ccx = ((glyph - 1) % CHAR_COLS);
387       int ccy = ((glyph - 1) / CHAR_COLS);
388
389       cx = ccx * w;
390       cy = (mp->real_char_rows - ccy - 1) * h;
391
392       if (do_fog)
393         {
394           GLfloat depth;
395           depth = (z / GRID_DEPTH) + 0.5;  /* z ratio from back/front      */
396           depth = 0.2 + (depth * 0.8);     /* scale to range [0.2 - 1.0]   */
397           brightness *= depth;             /* so no row goes all black.    */
398         }
399     }
400
401   {
402     GLfloat r, g, b, a;
403
404     if (highlight)
405       brightness *= 2;
406
407     if (!do_texture && !spinner_p)
408       r = b = 0, g = 1;
409     else
410       r = g = b = 1;
411
412     a = brightness;
413
414     /* If the glyph is very close to the screen (meaning it is very large,
415        and is about to splash into the screen and vanish) then start fading
416        it out, proportional to how close to the glass it is.
417     */
418     if (z > GRID_DEPTH/2)
419       {
420         GLfloat ratio = ((z - GRID_DEPTH/2) /
421                          ((GRID_DEPTH * SPLASH_RATIO) - GRID_DEPTH/2));
422         int i = ratio * WAVE_SIZE;
423
424         if (i < 0) i = 0;
425         else if (i >= WAVE_SIZE) i = WAVE_SIZE-1; 
426
427         a *= mp->brightness_ramp[i];
428       }
429
430     glColor4f (r,g,b,a);
431   }
432
433   glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
434   glNormal3f (0, 0, 1);
435   glTexCoord2f (cx,   cy);   glVertex3f (x,   y,   z);
436   glTexCoord2f (cx+w, cy);   glVertex3f (x+S, y,   z);
437   glTexCoord2f (cx+w, cy+h); glVertex3f (x+S, y+S, z);
438   glTexCoord2f (cx,   cy+h); glVertex3f (x,   y+S, z);
439   glEnd ();
440
441   if (wire && spinner_p)
442     {
443       glBegin (GL_LINES);
444       glVertex3f (x,   y,   z);
445       glVertex3f (x+S, y+S, z);
446       glVertex3f (x,   y+S, z);
447       glVertex3f (x+S, y,   z);
448       glEnd();
449     }
450
451   mi->polygon_count++;
452 }
453
454
455 /* Draw all the visible glyphs in the strip.
456  */
457 static void
458 draw_strip (ModeInfo *mi, strip *s)
459 {
460   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
461   int i;
462   for (i = 0; i < GRID_SIZE; i++)
463     {
464       int g = s->glyphs[i];
465       Bool below_p = (s->spinner_y >= i);
466
467       if (s->erasing_p)
468         below_p = !below_p;
469
470       if (g && below_p)       /* don't draw cells below the spinner */
471         {
472           GLfloat brightness;
473           if (!do_waves)
474             brightness = 1.0;
475           else
476             {
477               int j = WAVE_SIZE - ((i + (GRID_SIZE - s->wave_position))
478                                    % WAVE_SIZE);
479               brightness = mp->brightness_ramp[j];
480             }
481
482           draw_glyph (mi, g, s->highlight[i],
483                       s->x, s->y - i, s->z, brightness);
484         }
485     }
486
487   if (!s->erasing_p)
488     draw_glyph (mi, s->spinner_glyph, False,
489                 s->x, s->y - s->spinner_y, s->z, 1.0);
490 }
491
492
493 /* qsort comparator for sorting strips by z position */
494 static int
495 cmp_strips (const void *aa, const void *bb)
496 {
497   const strip *a = *(strip **) aa;
498   const strip *b = *(strip **) bb;
499   return ((int) (a->z * 10000) -
500           (int) (b->z * 10000));
501 }
502
503
504 /* Auto-tracking
505  */
506
507 static void
508 auto_track_init (ModeInfo *mi)
509 {
510   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
511   mp->last_view = 0;
512   mp->target_view = 0;
513   mp->view_x = nice_views[mp->last_view].x;
514   mp->view_y = nice_views[mp->last_view].y;
515   mp->view_steps = 100;
516   mp->view_tick = 0;
517   mp->auto_tracking_p = False;
518 }
519
520
521 static void
522 auto_track (ModeInfo *mi)
523 {
524   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
525
526   if (! do_rotate)
527     return;
528   if (mp->button_down_p)
529     return;
530
531   /* if we're not moving, maybe start moving.  Otherwise, do nothing. */
532   if (! mp->auto_tracking_p)
533     {
534       if (++mp->track_tick < 20/speed) return;
535       mp->track_tick = 0;
536       if (! (random() % 20))
537         mp->auto_tracking_p = True;
538       else
539         return;
540     }
541
542
543   {
544     GLfloat ox = nice_views[mp->last_view].x;
545     GLfloat oy = nice_views[mp->last_view].y;
546     GLfloat tx = nice_views[mp->target_view].x;
547     GLfloat ty = nice_views[mp->target_view].y;
548
549     /* move from A to B with sinusoidal deltas, so that it doesn't jerk
550        to a stop. */
551     GLfloat th = sin ((M_PI / 2) * (double) mp->view_tick / mp->view_steps);
552
553     mp->view_x = (ox + ((tx - ox) * th));
554     mp->view_y = (oy + ((ty - oy) * th));
555     mp->view_tick++;
556
557   if (mp->view_tick >= mp->view_steps)
558     {
559       mp->view_tick = 0;
560       mp->view_steps = (350.0 / speed);
561       mp->last_view = mp->target_view;
562       mp->target_view = (random() % (countof(nice_views) - 1)) + 1;
563       mp->auto_tracking_p = False;
564     }
565   }
566 }
567
568
569 /* Window management, etc
570  */
571 ENTRYPOINT void
572 reshape_matrix (ModeInfo *mi, int width, int height)
573 {
574   GLfloat h = (GLfloat) height / (GLfloat) width;
575
576   glViewport (0, 0, (GLint) width, (GLint) height);
577
578   glMatrixMode(GL_PROJECTION);
579   glLoadIdentity();
580   gluPerspective (80.0, 1/h, 1.0, 100);
581
582   glMatrixMode(GL_MODELVIEW);
583   glLoadIdentity();
584   gluLookAt( 0.0, 0.0, 25.0,
585              0.0, 0.0, 0.0,
586              0.0, 1.0, 0.0);
587
588   glClear(GL_COLOR_BUFFER_BIT);
589 }
590
591
592 ENTRYPOINT Bool
593 matrix_handle_event (ModeInfo *mi, XEvent *event)
594 {
595   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
596
597   if (event->xany.type == ButtonPress &&
598       event->xbutton.button == Button1)
599     {
600       mp->button_down_p = True;
601       return True;
602     }
603   else if (event->xany.type == ButtonRelease &&
604            event->xbutton.button == Button1)
605     {
606       mp->button_down_p = False;
607       return True;
608     }
609
610   return False;
611 }
612
613
614 #if 0
615 static Bool
616 bigendian (void)
617 {
618   union { int i; char c[sizeof(int)]; } u;
619   u.i = 1;
620   return !u.c[0];
621 }
622 #endif
623
624
625 /* The image with the characters in it is 512x598, meaning that it needs to
626    be copied into a 512x1024 texture.  But some machines can't handle textures
627    that large...  And it turns out that we aren't using most of the characters
628    in that image anyway, since this program doesn't do anything that makes use
629    of the full range of Latin1 characters.  So... this function tosses out the
630    last 32 of the Latin1 characters, resulting in a 512x506 image, which we
631    can then stuff in a 512x512 texture.  Voila.
632
633    If this hack ever grows into something that displays full Latin1 text,
634    well then, Something Else Will Need To Be Done.
635
636    Since currently GLMatrix does not run textclient / xscreensaver-text,
637    it's not an issue.  (XMatrix does that.)
638
639  */
640 static void
641 spank_image (matrix_configuration *mp, XImage *xi)
642 {
643   int ch = xi->height / CHAR_ROWS;
644   int cut = 2;
645   unsigned char *bits = (unsigned char *) xi->data;
646   unsigned char *from, *to, *s, *end;
647   int L = xi->bytes_per_line * ch;
648 /*  int i;*/
649
650   /* Copy row 12 into 10 (which really means, copy 2 into 0,
651      since texture data is upside down.).
652   */
653   to   = bits + (L * cut);
654   from = bits;
655   end  = from + L;
656   s    = from;
657   while (s < end)
658     *to++ = *s++;
659
660   /* Then, pull all the bits down by 2 rows.
661    */
662   to   = bits;
663   from = bits + (L * cut);
664   end  = bits + (L * CHAR_ROWS);
665   s    = from;
666   while (s < end)
667     *to++ = *s++;
668
669   /* And clear out the rest, for good measure.
670    */
671   from = bits + (L * (CHAR_ROWS - cut));
672   end  = bits + (L * CHAR_ROWS);
673   s    = from;
674   while (s < end)
675     *s++ = 0;
676
677   xi->height -= (cut * ch);
678   mp->real_char_rows -= cut;
679
680 # if 0
681   /* Finally, pull the map indexes back to match the new bits.
682    */
683   for (i = 0; i < countof(matrix_encoding); i++)
684     if (matrix_encoding[i] > (CHAR_COLS * (CHAR_ROWS - cut)))
685       matrix_encoding[i] -= (cut * CHAR_COLS);
686 # endif
687 }
688
689
690 static void
691 load_textures (ModeInfo *mi, Bool flip_p)
692 {
693   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
694   XImage *xi;
695   int x, y;
696   int cw, ch;
697   int orig_w, orig_h;
698
699   /* The Matrix XPM is 512x598 -- but GL texture sizes must be powers of 2.
700      So we waste some padding rows to round up.
701    */
702   xi = xpm_to_ximage (mi->dpy, mi->xgwa.visual, mi->xgwa.colormap,
703                       matrix3_xpm);
704   orig_w = xi->width;
705   orig_h = xi->height;
706   mp->real_char_rows = CHAR_ROWS;
707   spank_image (mp, xi);
708
709   if (xi->height != 512 && xi->height != 1024)
710     {
711       xi->height = (xi->height < 512 ? 512 : 1024);
712       xi->data = realloc (xi->data, xi->height * xi->bytes_per_line);
713       if (!xi->data)
714         {
715           fprintf(stderr, "%s: out of memory\n", progname);
716           exit(1);
717         }
718     }
719
720   if (xi->width != 512) abort();
721   if (xi->height != 512 && xi->height != 1024) abort();
722
723   /* char size in pixels */
724   cw = orig_w / CHAR_COLS;
725   ch = orig_h / CHAR_ROWS;
726
727   /* char size in ratio of final (padded) texture size */
728   mp->tex_char_width  = (GLfloat) cw / xi->width;
729   mp->tex_char_height = (GLfloat) ch / xi->height;
730
731   /* Flip each character's bits horizontally -- we could also just do this
732      by reversing the texture coordinates on the quads, but on some systems
733      that slows things down a lot.
734    */
735   if (flip_p)
736     {
737       int xx, col;
738       unsigned long buf[100];
739       for (y = 0; y < xi->height; y++)
740         for (col = 0, xx = 0; col < CHAR_COLS; col++, xx += cw)
741           {
742             for (x = 0; x < cw; x++)
743               buf[x] = XGetPixel (xi, xx+x, y);
744             for (x = 0; x < cw; x++)
745               XPutPixel (xi, xx+x, y, buf[cw-x-1]);
746           }
747     }
748
749   /* The pixmap is a color image with no transparency.  Set the texture's
750      alpha to be the green channel, and set the green channel to be 100%.
751    */
752   {
753     int rpos, gpos, bpos, apos;  /* bitfield positions */
754 #if 0
755     /* #### Cherub says that the little-endian case must be taken on MacOSX,
756             or else the colors/alpha are the wrong way around.  How can
757             that be the case?
758      */
759     if (bigendian())
760       rpos = 24, gpos = 16, bpos =  8, apos =  0;
761     else
762 #endif
763       rpos =  0, gpos =  8, bpos = 16, apos = 24;
764
765     for (y = 0; y < xi->height; y++)
766       for (x = 0; x < xi->width; x++)
767         {
768           unsigned long p = XGetPixel (xi, x, y);
769           unsigned char r = (p >> rpos) & 0xFF;
770           unsigned char g = (p >> gpos) & 0xFF;
771           unsigned char b = (p >> bpos) & 0xFF;
772           unsigned char a = g;
773           g = 0xFF;
774           p = (r << rpos) | (g << gpos) | (b << bpos) | (a << apos);
775           XPutPixel (xi, x, y, p);
776         }
777   }
778
779   /* Now load the texture into GL.
780    */
781   clear_gl_error();
782   glGenTextures (1, &mp->texture);
783
784   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
785   /* messes up -fps */
786   /* glPixelStorei (GL_UNPACK_ROW_LENGTH, xi->width);*/
787   glBindTexture (GL_TEXTURE_2D, mp->texture);
788   check_gl_error ("texture init");
789   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, xi->width, xi->height, 0, GL_RGBA,
790                 GL_UNSIGNED_INT_8_8_8_8_REV, xi->data);
791   {
792     char buf[255];
793     sprintf (buf, "creating %dx%d texture:", xi->width, xi->height);
794     check_gl_error (buf);
795   }
796
797   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
798   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
799
800   /* I'd expect CLAMP to be the thing to do here, but oddly, we get a
801      faint solid green border around the texture if it is *not* REPEAT!
802   */
803   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
804   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
805
806   glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
807   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
808   check_gl_error ("texture param");
809
810   XDestroyImage (xi);
811 }
812
813
814 ENTRYPOINT void 
815 init_matrix (ModeInfo *mi)
816 {
817   matrix_configuration *mp;
818   int wire = MI_IS_WIREFRAME(mi);
819   Bool flip_p = 0;
820   int i;
821
822   if (wire)
823     do_texture = False;
824
825   if (!mps) {
826     mps = (matrix_configuration *)
827       calloc (MI_NUM_SCREENS(mi), sizeof (matrix_configuration));
828     if (!mps) {
829       fprintf(stderr, "%s: out of memory\n", progname);
830       exit(1);
831     }
832   }
833
834   mp = &mps[MI_SCREEN(mi)];
835   mp->glx_context = init_GL(mi);
836
837   if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix"))
838     {
839       flip_p = 1;
840       mp->glyph_map = matrix_encoding;
841       mp->nglyphs   = countof(matrix_encoding);
842     }
843   else if (!strcasecmp (mode_str, "dna"))
844     {
845       flip_p = 0;
846       mp->glyph_map = dna_encoding;
847       mp->nglyphs   = countof(dna_encoding);
848     }
849   else if (!strcasecmp (mode_str, "bin") ||
850            !strcasecmp (mode_str, "binary"))
851     {
852       flip_p = 0;
853       mp->glyph_map = binary_encoding;
854       mp->nglyphs   = countof(binary_encoding);
855     }
856   else if (!strcasecmp (mode_str, "hex") ||
857            !strcasecmp (mode_str, "hexadecimal"))
858     {
859       flip_p = 0;
860       mp->glyph_map = hex_encoding;
861       mp->nglyphs   = countof(hex_encoding);
862     }
863   else if (!strcasecmp (mode_str, "dec") ||
864            !strcasecmp (mode_str, "decimal"))
865     {
866       flip_p = 0;
867       mp->glyph_map = decimal_encoding;
868       mp->nglyphs   = countof(decimal_encoding);
869     }
870   else
871     {
872       fprintf (stderr,
873            "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n",
874                progname, mode_str);
875       exit (1);
876     }
877
878   reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
879
880   glShadeModel(GL_SMOOTH);
881
882   glDisable(GL_DEPTH_TEST);
883   glDisable(GL_CULL_FACE);
884   glEnable(GL_NORMALIZE);
885
886   if (do_texture)
887     load_textures (mi, flip_p);
888
889   /* to scale coverage-percent to strips, this number looks about right... */
890   mp->nstrips = (int) (density * 2.2);
891   if      (mp->nstrips < 1)    mp->nstrips = 1;
892   else if (mp->nstrips > 2000) mp->nstrips = 2000;
893
894
895   mp->strips = calloc (mp->nstrips, sizeof(strip));
896   for (i = 0; i < mp->nstrips; i++)
897     {
898       strip *s = &mp->strips[i];
899       reset_strip (mi, s);
900
901       /* If we start all strips from zero at once, then the first few seconds
902          of the animation are much denser than normal.  So instead, set all
903          the initial strips to erase-mode with random starting positions.
904          As these die off at random speeds and are re-created, we'll get a
905          more consistent density. */
906       s->erasing_p = True;
907       s->spinner_y = frand(GRID_SIZE);
908       memset (s->glyphs, 0, sizeof(s->glyphs));  /* no visible glyphs */
909     }
910
911   /* Compute the brightness ramp.
912    */
913   for (i = 0; i < WAVE_SIZE; i++)
914     {
915       GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1));
916       j *= (M_PI / 2);       /* j ranges from 0.0 - PI/2  */
917       j = sin (j);           /* j ranges from 0.0 - 1.0   */
918       j = 0.2 + (j * 0.8);   /* j ranges from 0.2 - 1.0   */
919       mp->brightness_ramp[i] = j;
920       /* printf("%2d %8.2f\n", i, j); */
921     }
922
923
924   auto_track_init (mi);
925 }
926
927
928 #ifdef DEBUG
929
930 static void
931 draw_grid (ModeInfo *mi)
932 {
933   if (!MI_IS_WIREFRAME(mi))
934     {
935       glDisable(GL_TEXTURE_2D);
936       glDisable(GL_BLEND);
937     }
938   glPushMatrix();
939
940   glColor3f(1, 1, 1);
941   glBegin(GL_LINES);
942   glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0);
943   glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0);
944   glEnd();
945   glBegin(GL_LINE_LOOP);
946   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0);
947   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, 0);
948   glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, 0);
949   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0);
950   glEnd();
951   glBegin(GL_LINE_LOOP);
952   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
953   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
954   glVertex3f( GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
955   glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
956   glEnd();
957   glBegin(GL_LINE_LOOP);
958   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
959   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
960   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
961   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
962   glEnd();
963   glBegin(GL_LINES);
964   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
965   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, -GRID_DEPTH/2);
966   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
967   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2,  GRID_DEPTH/2);
968   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
969   glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, -GRID_DEPTH/2);
970   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
971   glVertex3f( GRID_SIZE/2,  GRID_SIZE/2,  GRID_DEPTH/2);
972   glEnd();
973   glPopMatrix();
974   if (!MI_IS_WIREFRAME(mi))
975     {
976       glEnable(GL_TEXTURE_2D);
977       glEnable(GL_BLEND);
978     }
979 }
980 #endif /* DEBUG */
981
982
983 ENTRYPOINT void
984 draw_matrix (ModeInfo *mi)
985 {
986   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
987   Display *dpy = MI_DISPLAY(mi);
988   Window window = MI_WINDOW(mi);
989   int i;
990
991   if (!mp->glx_context)
992     return;
993
994   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context));
995
996   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
997
998   glPushMatrix ();
999   glRotatef(current_device_rotation(), 0, 0, 1);
1000
1001   if (do_texture)
1002     {
1003       glEnable(GL_TEXTURE_2D);
1004       glEnable(GL_BLEND);
1005
1006       /* Jeff Epler points out:
1007          By using GL_ONE instead of GL_SRC_ONE_MINUS_ALPHA, glyphs are
1008          added to each other, so that a bright glyph with a darker one
1009          in front is a little brighter than the bright glyph alone.
1010        */
1011       glBlendFunc (GL_SRC_ALPHA, GL_ONE);
1012     }
1013
1014   if (do_rotate)
1015     {
1016       glRotatef (mp->view_x, 1, 0, 0);
1017       glRotatef (mp->view_y, 0, 1, 0);
1018     }
1019
1020 #ifdef DEBUG
1021 # if 0
1022   glScalef(0.5, 0.5, 0.5);
1023 # endif
1024 # if 0
1025   glRotatef(-30, 0, 1, 0); 
1026 # endif
1027   draw_grid (mi);
1028 #endif
1029
1030   mi->polygon_count = 0;
1031
1032   /* Render (and tick) each strip, starting at the back
1033      (draw the ones farthest from the camera first, to make
1034      the alpha transparency work out right.)
1035    */
1036   {
1037     strip **sorted = malloc (mp->nstrips * sizeof(*sorted));
1038     for (i = 0; i < mp->nstrips; i++)
1039       sorted[i] = &mp->strips[i];
1040     qsort (sorted, i, sizeof(*sorted), cmp_strips);
1041
1042     for (i = 0; i < mp->nstrips; i++)
1043       {
1044         strip *s = sorted[i];
1045         tick_strip (mi, s);
1046         draw_strip (mi, s);
1047       }
1048     free (sorted);
1049   }
1050
1051   auto_track (mi);
1052
1053 #if 0
1054   glBegin(GL_QUADS);
1055   glColor3f(1,1,1);
1056   glTexCoord2f (0,0);  glVertex3f(-15,-15,0);
1057   glTexCoord2f (0,1);  glVertex3f(-15,15,0);
1058   glTexCoord2f (1,1);  glVertex3f(15,15,0);
1059   glTexCoord2f (1,0);  glVertex3f(15,-15,0);
1060   glEnd();
1061 #endif
1062
1063   glPopMatrix ();
1064
1065   if (mi->fps_p) do_fps (mi);
1066   glFinish();
1067
1068   glXSwapBuffers(dpy, window);
1069 }
1070
1071 XSCREENSAVER_MODULE_2 ("GLMatrix", glmatrix, matrix)
1072
1073 #endif /* USE_GL */