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