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