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