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