From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[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 static void
637 spank_image (matrix_configuration *mp, XImage *xi)
638 {
639   int ch = xi->height / CHAR_ROWS;
640   int cut = 2;
641   unsigned char *bits = (unsigned char *) xi->data;
642   unsigned char *from, *to, *s, *end;
643   int L = xi->bytes_per_line * ch;
644 /*  int i;*/
645
646   /* Copy row 12 into 10 (which really means, copy 2 into 0,
647      since texture data is upside down.).
648   */
649   to   = bits + (L * cut);
650   from = bits;
651   end  = from + L;
652   s    = from;
653   while (s < end)
654     *to++ = *s++;
655
656   /* Then, pull all the bits down by 2 rows.
657    */
658   to   = bits;
659   from = bits + (L * cut);
660   end  = bits + (L * CHAR_ROWS);
661   s    = from;
662   while (s < end)
663     *to++ = *s++;
664
665   /* And clear out the rest, for good measure.
666    */
667   from = bits + (L * (CHAR_ROWS - cut));
668   end  = bits + (L * CHAR_ROWS);
669   s    = from;
670   while (s < end)
671     *s++ = 0;
672
673   xi->height -= (cut * ch);
674   mp->real_char_rows -= cut;
675
676 # if 0
677   /* Finally, pull the map indexes back to match the new bits.
678    */
679   for (i = 0; i < countof(matrix_encoding); i++)
680     if (matrix_encoding[i] > (CHAR_COLS * (CHAR_ROWS - cut)))
681       matrix_encoding[i] -= (cut * CHAR_COLS);
682 # endif
683 }
684
685
686 static void
687 load_textures (ModeInfo *mi, Bool flip_p)
688 {
689   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
690   XImage *xi;
691   int x, y;
692   int cw, ch;
693   int orig_w, orig_h;
694
695   /* The Matrix XPM is 512x598 -- but GL texture sizes must be powers of 2.
696      So we waste some padding rows to round up.
697    */
698   xi = xpm_to_ximage (mi->dpy, mi->xgwa.visual, mi->xgwa.colormap,
699                       matrix3_xpm);
700   orig_w = xi->width;
701   orig_h = xi->height;
702   mp->real_char_rows = CHAR_ROWS;
703   spank_image (mp, xi);
704
705   if (xi->height != 512 && xi->height != 1024)
706     {
707       xi->height = (xi->height < 512 ? 512 : 1024);
708       xi->data = realloc (xi->data, xi->height * xi->bytes_per_line);
709       if (!xi->data)
710         {
711           fprintf(stderr, "%s: out of memory\n", progname);
712           exit(1);
713         }
714     }
715
716   if (xi->width != 512) abort();
717   if (xi->height != 512 && xi->height != 1024) abort();
718
719   /* char size in pixels */
720   cw = orig_w / CHAR_COLS;
721   ch = orig_h / CHAR_ROWS;
722
723   /* char size in ratio of final (padded) texture size */
724   mp->tex_char_width  = (GLfloat) cw / xi->width;
725   mp->tex_char_height = (GLfloat) ch / xi->height;
726
727   /* Flip each character's bits horizontally -- we could also just do this
728      by reversing the texture coordinates on the quads, but on some systems
729      that slows things down a lot.
730    */
731   if (flip_p)
732     {
733       int xx, col;
734       unsigned long buf[100];
735       for (y = 0; y < xi->height; y++)
736         for (col = 0, xx = 0; col < CHAR_COLS; col++, xx += cw)
737           {
738             for (x = 0; x < cw; x++)
739               buf[x] = XGetPixel (xi, xx+x, y);
740             for (x = 0; x < cw; x++)
741               XPutPixel (xi, xx+x, y, buf[cw-x-1]);
742           }
743     }
744
745   /* The pixmap is a color image with no transparency.  Set the texture's
746      alpha to be the green channel, and set the green channel to be 100%.
747    */
748   {
749     int rpos, gpos, bpos, apos;  /* bitfield positions */
750 #if 0
751     /* #### Cherub says that the little-endian case must be taken on MacOSX,
752             or else the colors/alpha are the wrong way around.  How can
753             that be the case?
754      */
755     if (bigendian())
756       rpos = 24, gpos = 16, bpos =  8, apos =  0;
757     else
758 #endif
759       rpos =  0, gpos =  8, bpos = 16, apos = 24;
760
761     for (y = 0; y < xi->height; y++)
762       for (x = 0; x < xi->width; x++)
763         {
764           unsigned long p = XGetPixel (xi, x, y);
765           unsigned char r = (p >> rpos) & 0xFF;
766           unsigned char g = (p >> gpos) & 0xFF;
767           unsigned char b = (p >> bpos) & 0xFF;
768           unsigned char a = g;
769           g = 0xFF;
770           p = (r << rpos) | (g << gpos) | (b << bpos) | (a << apos);
771           XPutPixel (xi, x, y, p);
772         }
773   }
774
775   /* Now load the texture into GL.
776    */
777   clear_gl_error();
778   glGenTextures (1, &mp->texture);
779
780   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
781   /* messes up -fps */
782   /* glPixelStorei (GL_UNPACK_ROW_LENGTH, xi->width);*/
783   glBindTexture (GL_TEXTURE_2D, mp->texture);
784   check_gl_error ("texture init");
785   glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, xi->width, xi->height, 0, GL_RGBA,
786                 GL_UNSIGNED_INT_8_8_8_8_REV, xi->data);
787   {
788     char buf[255];
789     sprintf (buf, "creating %dx%d texture:", xi->width, xi->height);
790     check_gl_error (buf);
791   }
792
793   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
794   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
795
796   /* I'd expect CLAMP to be the thing to do here, but oddly, we get a
797      faint solid green border around the texture if it is *not* REPEAT!
798   */
799   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
800   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
801
802   glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
803   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
804   check_gl_error ("texture param");
805
806   XDestroyImage (xi);
807 }
808
809
810 ENTRYPOINT void 
811 init_matrix (ModeInfo *mi)
812 {
813   matrix_configuration *mp;
814   int wire = MI_IS_WIREFRAME(mi);
815   Bool flip_p = 0;
816   int i;
817
818   if (wire)
819     do_texture = False;
820
821   if (!mps) {
822     mps = (matrix_configuration *)
823       calloc (MI_NUM_SCREENS(mi), sizeof (matrix_configuration));
824     if (!mps) {
825       fprintf(stderr, "%s: out of memory\n", progname);
826       exit(1);
827     }
828   }
829
830   mp = &mps[MI_SCREEN(mi)];
831   mp->glx_context = init_GL(mi);
832
833   if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix"))
834     {
835       flip_p = 1;
836       mp->glyph_map = matrix_encoding;
837       mp->nglyphs   = countof(matrix_encoding);
838     }
839   else if (!strcasecmp (mode_str, "dna"))
840     {
841       flip_p = 0;
842       mp->glyph_map = dna_encoding;
843       mp->nglyphs   = countof(dna_encoding);
844     }
845   else if (!strcasecmp (mode_str, "bin") ||
846            !strcasecmp (mode_str, "binary"))
847     {
848       flip_p = 0;
849       mp->glyph_map = binary_encoding;
850       mp->nglyphs   = countof(binary_encoding);
851     }
852   else if (!strcasecmp (mode_str, "hex") ||
853            !strcasecmp (mode_str, "hexadecimal"))
854     {
855       flip_p = 0;
856       mp->glyph_map = hex_encoding;
857       mp->nglyphs   = countof(hex_encoding);
858     }
859   else if (!strcasecmp (mode_str, "dec") ||
860            !strcasecmp (mode_str, "decimal"))
861     {
862       flip_p = 0;
863       mp->glyph_map = decimal_encoding;
864       mp->nglyphs   = countof(decimal_encoding);
865     }
866   else
867     {
868       fprintf (stderr,
869            "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n",
870                progname, mode_str);
871       exit (1);
872     }
873
874   reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
875
876   glShadeModel(GL_SMOOTH);
877
878   glDisable(GL_DEPTH_TEST);
879   glDisable(GL_CULL_FACE);
880   glEnable(GL_NORMALIZE);
881
882   if (do_texture)
883     load_textures (mi, flip_p);
884
885   /* to scale coverage-percent to strips, this number looks about right... */
886   mp->nstrips = (int) (density * 2.2);
887   if      (mp->nstrips < 1)    mp->nstrips = 1;
888   else if (mp->nstrips > 2000) mp->nstrips = 2000;
889
890
891   mp->strips = calloc (mp->nstrips, sizeof(strip));
892   for (i = 0; i < mp->nstrips; i++)
893     {
894       strip *s = &mp->strips[i];
895       reset_strip (mi, s);
896
897       /* If we start all strips from zero at once, then the first few seconds
898          of the animation are much denser than normal.  So instead, set all
899          the initial strips to erase-mode with random starting positions.
900          As these die off at random speeds and are re-created, we'll get a
901          more consistent density. */
902       s->erasing_p = True;
903       s->spinner_y = frand(GRID_SIZE);
904       memset (s->glyphs, 0, sizeof(s->glyphs));  /* no visible glyphs */
905     }
906
907   /* Compute the brightness ramp.
908    */
909   for (i = 0; i < WAVE_SIZE; i++)
910     {
911       GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1));
912       j *= (M_PI / 2);       /* j ranges from 0.0 - PI/2  */
913       j = sin (j);           /* j ranges from 0.0 - 1.0   */
914       j = 0.2 + (j * 0.8);   /* j ranges from 0.2 - 1.0   */
915       mp->brightness_ramp[i] = j;
916       /* printf("%2d %8.2f\n", i, j); */
917     }
918
919
920   auto_track_init (mi);
921 }
922
923
924 #ifdef DEBUG
925
926 static void
927 draw_grid (ModeInfo *mi)
928 {
929   if (!MI_IS_WIREFRAME(mi))
930     {
931       glDisable(GL_TEXTURE_2D);
932       glDisable(GL_BLEND);
933     }
934   glPushMatrix();
935
936   glColor3f(1, 1, 1);
937   glBegin(GL_LINES);
938   glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0);
939   glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0);
940   glEnd();
941   glBegin(GL_LINE_LOOP);
942   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0);
943   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, 0);
944   glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, 0);
945   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0);
946   glEnd();
947   glBegin(GL_LINE_LOOP);
948   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
949   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
950   glVertex3f( GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
951   glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
952   glEnd();
953   glBegin(GL_LINE_LOOP);
954   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
955   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
956   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
957   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
958   glEnd();
959   glBegin(GL_LINES);
960   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
961   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, -GRID_DEPTH/2);
962   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2,  GRID_DEPTH/2);
963   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2,  GRID_DEPTH/2);
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   glEnd();
969   glPopMatrix();
970   if (!MI_IS_WIREFRAME(mi))
971     {
972       glEnable(GL_TEXTURE_2D);
973       glEnable(GL_BLEND);
974     }
975 }
976 #endif /* DEBUG */
977
978
979 ENTRYPOINT void
980 draw_matrix (ModeInfo *mi)
981 {
982   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
983   Display *dpy = MI_DISPLAY(mi);
984   Window window = MI_WINDOW(mi);
985   int i;
986
987   if (!mp->glx_context)
988     return;
989
990   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context));
991
992   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
993
994   glPushMatrix ();
995   glRotatef(current_device_rotation(), 0, 0, 1);
996
997   if (do_texture)
998     {
999       glEnable(GL_TEXTURE_2D);
1000       glEnable(GL_BLEND);
1001
1002       /* Jeff Epler points out:
1003          By using GL_ONE instead of GL_SRC_ONE_MINUS_ALPHA, glyphs are
1004          added to each other, so that a bright glyph with a darker one
1005          in front is a little brighter than the bright glyph alone.
1006        */
1007       glBlendFunc (GL_SRC_ALPHA, GL_ONE);
1008     }
1009
1010   if (do_rotate)
1011     {
1012       glRotatef (mp->view_x, 1, 0, 0);
1013       glRotatef (mp->view_y, 0, 1, 0);
1014     }
1015
1016 #ifdef DEBUG
1017 # if 0
1018   glScalef(0.5, 0.5, 0.5);
1019 # endif
1020 # if 0
1021   glRotatef(-30, 0, 1, 0); 
1022 # endif
1023   draw_grid (mi);
1024 #endif
1025
1026   mi->polygon_count = 0;
1027
1028   /* Render (and tick) each strip, starting at the back
1029      (draw the ones farthest from the camera first, to make
1030      the alpha transparency work out right.)
1031    */
1032   {
1033     strip **sorted = malloc (mp->nstrips * sizeof(*sorted));
1034     for (i = 0; i < mp->nstrips; i++)
1035       sorted[i] = &mp->strips[i];
1036     qsort (sorted, i, sizeof(*sorted), cmp_strips);
1037
1038     for (i = 0; i < mp->nstrips; i++)
1039       {
1040         strip *s = sorted[i];
1041         tick_strip (mi, s);
1042         draw_strip (mi, s);
1043       }
1044     free (sorted);
1045   }
1046
1047   auto_track (mi);
1048
1049 #if 0
1050   glBegin(GL_QUADS);
1051   glColor3f(1,1,1);
1052   glTexCoord2f (0,0);  glVertex3f(-15,-15,0);
1053   glTexCoord2f (0,1);  glVertex3f(-15,15,0);
1054   glTexCoord2f (1,1);  glVertex3f(15,15,0);
1055   glTexCoord2f (1,0);  glVertex3f(15,-15,0);
1056   glEnd();
1057 #endif
1058
1059   glPopMatrix ();
1060
1061   if (mi->fps_p) do_fps (mi);
1062   glFinish();
1063
1064   glXSwapBuffers(dpy, window);
1065 }
1066
1067 XSCREENSAVER_MODULE_2 ("GLMatrix", glmatrix, matrix)
1068
1069 #endif /* USE_GL */