From http://www.jwz.org/xscreensaver/xscreensaver-5.37.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    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   MI_INIT (mi, mps, NULL);
826
827   mp = &mps[MI_SCREEN(mi)];
828   mp->glx_context = init_GL(mi);
829
830   if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix"))
831     {
832       flip_p = 1;
833       mp->glyph_map = matrix_encoding;
834       mp->nglyphs   = countof(matrix_encoding);
835     }
836   else if (!strcasecmp (mode_str, "dna"))
837     {
838       flip_p = 0;
839       mp->glyph_map = dna_encoding;
840       mp->nglyphs   = countof(dna_encoding);
841     }
842   else if (!strcasecmp (mode_str, "bin") ||
843            !strcasecmp (mode_str, "binary"))
844     {
845       flip_p = 0;
846       mp->glyph_map = binary_encoding;
847       mp->nglyphs   = countof(binary_encoding);
848     }
849   else if (!strcasecmp (mode_str, "hex") ||
850            !strcasecmp (mode_str, "hexadecimal"))
851     {
852       flip_p = 0;
853       mp->glyph_map = hex_encoding;
854       mp->nglyphs   = countof(hex_encoding);
855     }
856   else if (!strcasecmp (mode_str, "dec") ||
857            !strcasecmp (mode_str, "decimal"))
858     {
859       flip_p = 0;
860       mp->glyph_map = decimal_encoding;
861       mp->nglyphs   = countof(decimal_encoding);
862     }
863   else
864     {
865       fprintf (stderr,
866            "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n",
867                progname, mode_str);
868       exit (1);
869     }
870
871   reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
872
873   glShadeModel(GL_SMOOTH);
874
875   glDisable(GL_DEPTH_TEST);
876   glDisable(GL_CULL_FACE);
877   glEnable(GL_NORMALIZE);
878
879   if (do_texture)
880     load_textures (mi, flip_p);
881
882   /* to scale coverage-percent to strips, this number looks about right... */
883   mp->nstrips = (int) (density * 2.2);
884   if      (mp->nstrips < 1)    mp->nstrips = 1;
885   else if (mp->nstrips > 2000) mp->nstrips = 2000;
886
887
888   mp->strips = calloc (mp->nstrips, sizeof(strip));
889   for (i = 0; i < mp->nstrips; i++)
890     {
891       strip *s = &mp->strips[i];
892       reset_strip (mi, s);
893
894       /* If we start all strips from zero at once, then the first few seconds
895          of the animation are much denser than normal.  So instead, set all
896          the initial strips to erase-mode with random starting positions.
897          As these die off at random speeds and are re-created, we'll get a
898          more consistent density. */
899       s->erasing_p = True;
900       s->spinner_y = frand(GRID_SIZE);
901       memset (s->glyphs, 0, sizeof(s->glyphs));  /* no visible glyphs */
902     }
903
904   /* Compute the brightness ramp.
905    */
906   for (i = 0; i < WAVE_SIZE; i++)
907     {
908       GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1));
909       j *= (M_PI / 2);       /* j ranges from 0.0 - PI/2  */
910       j = sin (j);           /* j ranges from 0.0 - 1.0   */
911       j = 0.2 + (j * 0.8);   /* j ranges from 0.2 - 1.0   */
912       mp->brightness_ramp[i] = j;
913       /* printf("%2d %8.2f\n", i, j); */
914     }
915
916
917   auto_track_init (mi);
918 }
919
920
921 #ifdef DEBUG
922
923 static void
924 draw_grid (ModeInfo *mi)
925 {
926   if (!MI_IS_WIREFRAME(mi))
927     {
928       glDisable(GL_TEXTURE_2D);
929       glDisable(GL_BLEND);
930     }
931   glPushMatrix();
932
933   glColor3f(1, 1, 1);
934   glBegin(GL_LINES);
935   glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0);
936   glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0);
937   glEnd();
938   glBegin(GL_LINE_LOOP);
939   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0);
940   glVertex3f(-GRID_SIZE/2,  GRID_SIZE/2, 0);
941   glVertex3f( GRID_SIZE/2,  GRID_SIZE/2, 0);
942   glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0);
943   glEnd();
944   glBegin(GL_LINE_LOOP);
945   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
946   glVertex3f(-GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
947   glVertex3f( GRID_SIZE/2, GRID_SIZE/2,  GRID_DEPTH/2);
948   glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2);
949   glEnd();
950   glBegin(GL_LINE_LOOP);
951   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
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   glEnd();
956   glBegin(GL_LINES);
957   glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2);
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   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   glEnd();
966   glPopMatrix();
967   if (!MI_IS_WIREFRAME(mi))
968     {
969       glEnable(GL_TEXTURE_2D);
970       glEnable(GL_BLEND);
971     }
972 }
973 #endif /* DEBUG */
974
975
976 ENTRYPOINT void
977 draw_matrix (ModeInfo *mi)
978 {
979   matrix_configuration *mp = &mps[MI_SCREEN(mi)];
980   Display *dpy = MI_DISPLAY(mi);
981   Window window = MI_WINDOW(mi);
982   int i;
983
984   if (!mp->glx_context)
985     return;
986
987   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context));
988
989   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
990
991   glPushMatrix ();
992   glRotatef(current_device_rotation(), 0, 0, 1);
993
994   if (do_texture)
995     {
996       glEnable(GL_TEXTURE_2D);
997       glEnable(GL_BLEND);
998
999       /* Jeff Epler points out:
1000          By using GL_ONE instead of GL_SRC_ONE_MINUS_ALPHA, glyphs are
1001          added to each other, so that a bright glyph with a darker one
1002          in front is a little brighter than the bright glyph alone.
1003        */
1004       glBlendFunc (GL_SRC_ALPHA, GL_ONE);
1005     }
1006
1007   if (do_rotate)
1008     {
1009       glRotatef (mp->view_x, 1, 0, 0);
1010       glRotatef (mp->view_y, 0, 1, 0);
1011     }
1012
1013 #ifdef DEBUG
1014 # if 0
1015   glScalef(0.5, 0.5, 0.5);
1016 # endif
1017 # if 0
1018   glRotatef(-30, 0, 1, 0); 
1019 # endif
1020   draw_grid (mi);
1021 #endif
1022
1023   mi->polygon_count = 0;
1024
1025   /* Render (and tick) each strip, starting at the back
1026      (draw the ones farthest from the camera first, to make
1027      the alpha transparency work out right.)
1028    */
1029   {
1030     strip **sorted = malloc (mp->nstrips * sizeof(*sorted));
1031     for (i = 0; i < mp->nstrips; i++)
1032       sorted[i] = &mp->strips[i];
1033     qsort (sorted, i, sizeof(*sorted), cmp_strips);
1034
1035     for (i = 0; i < mp->nstrips; i++)
1036       {
1037         strip *s = sorted[i];
1038         tick_strip (mi, s);
1039         draw_strip (mi, s);
1040       }
1041     free (sorted);
1042   }
1043
1044   auto_track (mi);
1045
1046 #if 0
1047   glBegin(GL_QUADS);
1048   glColor3f(1,1,1);
1049   glTexCoord2f (0,0);  glVertex3f(-15,-15,0);
1050   glTexCoord2f (0,1);  glVertex3f(-15,15,0);
1051   glTexCoord2f (1,1);  glVertex3f(15,15,0);
1052   glTexCoord2f (1,0);  glVertex3f(15,-15,0);
1053   glEnd();
1054 #endif
1055
1056   glPopMatrix ();
1057
1058   if (mi->fps_p) do_fps (mi);
1059   glFinish();
1060
1061   glXSwapBuffers(dpy, window);
1062 }
1063
1064 XSCREENSAVER_MODULE_2 ("GLMatrix", glmatrix, matrix)
1065
1066 #endif /* USE_GL */