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