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