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