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