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