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