/* glmatrix, Copyright (c) 2003-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * GLMatrix -- simulate the text scrolls from the movie "The Matrix". * * This program does a 3D rendering of the dropping characters that * appeared in the title sequences of the movies. See also `xmatrix' * for a simulation of what the computer monitors actually *in* the * movie did. */ #define DEFAULTS "*delay: 30000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ # define free_matrix 0 # define release_matrix 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #undef BELLRAND #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3) #include "xlockmore.h" #include "ximage-loader.h" #include "images/gen/matrix3_png.h" #ifdef USE_GL /* whole file */ #define DEF_SPEED "1.0" #define DEF_DENSITY "20" #define DEF_CLOCK "False" #define DEF_FOG "True" #define DEF_WAVES "True" #define DEF_ROTATE "True" #define DEF_TEXTURE "True" #define DEF_MODE "Matrix" #define DEF_TIMEFMT " %l%M%p " #define CHAR_COLS 16 #define CHAR_ROWS 13 static const int matrix_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, # if 0 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207 # else 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175 # endif }; static const int decimal_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 }; static const int hex_encoding[] = { 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 33, 34, 35, 36, 37, 38 }; static const int binary_encoding[] = { 16, 17 }; static const int dna_encoding[] = { 33, 35, 39, 52 }; static const unsigned char char_map[256] = { 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, /* 16 */ 0, 1, 2, 96, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 32 */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* 48 */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* 64 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* 80 */ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, /* 96 */ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, /* 112 */ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 128 */ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 144 */ 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, /* 160 */ 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, /* 176 */ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, /* 192 */ 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, /* 208 */ #if 0 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, /* 224 */ 176,177,178,195,180,181,182,183,184,185,186,187,188,189,190,191 /* 240 */ #else /* see spank_image() */ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 224 */ 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, /* 240 */ #endif }; #define CURSOR_GLYPH 97 /* #define DEBUG */ #define GRID_SIZE 70 /* width and height of the arena */ #define GRID_DEPTH 35 /* depth of the arena */ #define WAVE_SIZE 22 /* periodicity of color (brightness) waves */ #define SPLASH_RATIO 0.7 /* ratio of GRID_DEPTH where chars hit the screen */ static const struct { GLfloat x, y; } nice_views[] = { { 0, 0 }, { 0, -20 }, /* this is a list of viewer rotations that look nice. */ { 0, 20 }, /* every now and then we switch to a new one. */ { 25, 0 }, /* (but we only use the first one at start-up.) */ {-25, 0 }, { 25, 20 }, {-25, 20 }, { 25, -20 }, {-25, -20 }, { 10, 0 }, {-10, 0 }, { 0, 0 }, /* prefer these */ { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, }; typedef struct { GLfloat x, y, z; /* position of strip */ GLfloat dx, dy, dz; /* velocity of strip */ Bool erasing_p; /* Whether this strip is on its way out. */ int spinner_glyph; /* the bottommost glyph -- the feeder */ GLfloat spinner_y; /* where on the strip the bottom glyph is */ GLfloat spinner_speed; /* how fast the bottom glyph drops */ int glyphs[GRID_SIZE]; /* the other glyphs on the strip, which will be revealed by the dropping spinner. 0 means no glyph; negative means "spinner". If non-zero, real value is abs(G)-1. */ Bool highlight[GRID_SIZE]; /* some glyphs may be highlighted */ int spin_speed; /* Rotate all spinners every this-many frames */ int spin_tick; /* frame counter */ int wave_position; /* Waves of brightness wash down the strip. */ int wave_speed; /* every this-many frames. */ int wave_tick; /* frame counter. */ } strip; typedef struct { GLXContext *glx_context; Bool button_down_p; GLuint texture; int nstrips; strip *strips; const int *glyph_map; int nglyphs; GLfloat tex_char_width, tex_char_height; /* auto-tracking direction of view */ int last_view, target_view; GLfloat view_x, view_y; int view_steps, view_tick; Bool auto_tracking_p; int track_tick; int real_char_rows; GLfloat brightness_ramp[WAVE_SIZE]; } matrix_configuration; static matrix_configuration *mps = NULL; static GLfloat speed; static GLfloat density; static Bool do_clock; static char *timefmt; static Bool do_fog; static Bool do_waves; static Bool do_rotate; static Bool do_texture; static char *mode_str; static XrmOptionDescRec opts[] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-density", ".density", XrmoptionSepArg, 0 }, { "-mode", ".mode", XrmoptionSepArg, 0 }, { "-binary", ".mode", XrmoptionNoArg, "binary" }, { "-hexadecimal", ".mode", XrmoptionNoArg, "hexadecimal" }, { "-decimal", ".mode", XrmoptionNoArg, "decimal" }, { "-dna", ".mode", XrmoptionNoArg, "dna" }, { "-clock", ".clock", XrmoptionNoArg, "True" }, { "+clock", ".clock", XrmoptionNoArg, "False" }, { "-timefmt", ".timefmt", XrmoptionSepArg, 0 }, { "-fog", ".fog", XrmoptionNoArg, "True" }, { "+fog", ".fog", XrmoptionNoArg, "False" }, { "-waves", ".waves", XrmoptionNoArg, "True" }, { "+waves", ".waves", XrmoptionNoArg, "False" }, { "-rotate", ".rotate", XrmoptionNoArg, "True" }, { "+rotate", ".rotate", XrmoptionNoArg, "False" }, {"-texture", ".texture", XrmoptionNoArg, "True" }, {"+texture", ".texture", XrmoptionNoArg, "False" }, }; static argtype vars[] = { {&mode_str, "mode", "Mode", DEF_MODE, t_String}, {&speed, "speed", "Speed", DEF_SPEED, t_Float}, {&density, "density", "Density", DEF_DENSITY, t_Float}, {&do_clock, "clock", "Clock", DEF_CLOCK, t_Bool}, {&timefmt, "timefmt", "Timefmt", DEF_TIMEFMT, t_String}, {&do_fog, "fog", "Fog", DEF_FOG, t_Bool}, {&do_waves, "waves", "Waves", DEF_WAVES, t_Bool}, {&do_rotate, "rotate", "Rotate", DEF_ROTATE, t_Bool}, {&do_texture, "texture", "Texture", DEF_TEXTURE, t_Bool}, }; ENTRYPOINT ModeSpecOpt matrix_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Re-randomize the state of one strip. */ static void reset_strip (ModeInfo *mi, strip *s) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; int i; Bool time_displayed_p = False; /* never display time twice in one strip */ memset (s, 0, sizeof(*s)); s->x = (GLfloat) (frand(GRID_SIZE) - (GRID_SIZE/2)); s->y = (GLfloat) (GRID_SIZE/2 + BELLRAND(0.5)); /* shift top slightly */ s->z = (GLfloat) (GRID_DEPTH * 0.2) - frand (GRID_DEPTH * 0.7); s->spinner_y = 0; s->dx = 0; /* s->dx = ((BELLRAND(0.01) - 0.005) * speed); */ s->dy = 0; s->dz = (BELLRAND(0.02) * speed); s->spinner_speed = (BELLRAND(0.3) * speed); s->spin_speed = (int) BELLRAND(2.0 / speed) + 1; s->spin_tick = 0; s->wave_position = 0; s->wave_speed = (int) BELLRAND(3.0 / speed) + 1; s->wave_tick = 0; for (i = 0; i < GRID_SIZE; i++) if (do_clock && !time_displayed_p && (i < GRID_SIZE-5) && /* display approx. once per 5 strips */ !(random() % (GRID_SIZE-5)*5)) { int j; char text[80]; time_t now = time ((time_t *) 0); struct tm *tm = localtime (&now); strftime (text, sizeof(text)-1, timefmt, tm); /* render time into the strip */ for (j = 0; j < strlen(text) && i < GRID_SIZE; j++, i++) { s->glyphs[i] = char_map [((unsigned char *) text)[j]] + 1; s->highlight[i] = True; } time_displayed_p = True; } else { int draw_p = (random() % 7); int spin_p = (draw_p && !(random() % 20)); int g = (draw_p ? mp->glyph_map[(random() % mp->nglyphs)] + 1 : 0); if (spin_p) g = -g; s->glyphs[i] = g; s->highlight[i] = False; } s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1); } /* Animate the strip one step. Reset if it has reached the bottom. */ static void tick_strip (ModeInfo *mi, strip *s) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; int i; if (mp->button_down_p) return; s->x += s->dx; s->y += s->dy; s->z += s->dz; if (s->z > GRID_DEPTH * SPLASH_RATIO) /* splashed into screen */ { reset_strip (mi, s); return; } s->spinner_y += s->spinner_speed; if (s->spinner_y >= GRID_SIZE) { if (s->erasing_p) { reset_strip (mi, s); return; } else { s->erasing_p = True; s->spinner_y = 0; s->spinner_speed /= 2; /* erase it slower than we drew it */ } } /* Spin the spinners. */ s->spin_tick++; if (s->spin_tick > s->spin_speed) { s->spin_tick = 0; s->spinner_glyph = - (mp->glyph_map[(random() % mp->nglyphs)] + 1); for (i = 0; i < GRID_SIZE; i++) if (s->glyphs[i] < 0) { s->glyphs[i] = -(mp->glyph_map[(random() % mp->nglyphs)] + 1); if (! (random() % 800)) /* sometimes they stop spinning */ s->glyphs[i] = -s->glyphs[i]; } } /* Move the color (brightness) wave. */ s->wave_tick++; if (s->wave_tick > s->wave_speed) { s->wave_tick = 0; s->wave_position++; if (s->wave_position >= WAVE_SIZE) s->wave_position = 0; } } /* Draw a single character at the given position and brightness. */ static void draw_glyph (ModeInfo *mi, int glyph, Bool highlight, GLfloat x, GLfloat y, GLfloat z, GLfloat brightness) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); GLfloat w = mp->tex_char_width; GLfloat h = mp->tex_char_height; GLfloat cx = 0, cy = 0; GLfloat S = 1; Bool spinner_p = (glyph < 0); if (glyph == 0) abort(); if (glyph < 0) glyph = -glyph; if (spinner_p) brightness *= 1.5; if (!do_texture) { S = 0.8; x += 0.1; y += 0.1; } else { int ccx = ((glyph - 1) % CHAR_COLS); int ccy = ((glyph - 1) / CHAR_COLS); cx = ccx * w; cy = (mp->real_char_rows - ccy - 1) * h; if (do_fog) { GLfloat depth; depth = (z / GRID_DEPTH) + 0.5; /* z ratio from back/front */ depth = 0.2 + (depth * 0.8); /* scale to range [0.2 - 1.0] */ brightness *= depth; /* so no row goes all black. */ } } { GLfloat r, g, b, a; if (highlight) brightness *= 2; if (!do_texture && !spinner_p) r = b = 0, g = 1; else r = g = b = 1; a = brightness; /* If the glyph is very close to the screen (meaning it is very large, and is about to splash into the screen and vanish) then start fading it out, proportional to how close to the glass it is. */ if (z > GRID_DEPTH/2) { GLfloat ratio = ((z - GRID_DEPTH/2) / ((GRID_DEPTH * SPLASH_RATIO) - GRID_DEPTH/2)); int i = ratio * WAVE_SIZE; if (i < 0) i = 0; else if (i >= WAVE_SIZE) i = WAVE_SIZE-1; a *= mp->brightness_ramp[i]; } glColor4f (r,g,b,a); } glBegin (wire ? GL_LINE_LOOP : GL_QUADS); glNormal3f (0, 0, 1); glTexCoord2f (cx, cy); glVertex3f (x, y, z); glTexCoord2f (cx+w, cy); glVertex3f (x+S, y, z); glTexCoord2f (cx+w, cy+h); glVertex3f (x+S, y+S, z); glTexCoord2f (cx, cy+h); glVertex3f (x, y+S, z); glEnd (); if (wire && spinner_p) { glBegin (GL_LINES); glVertex3f (x, y, z); glVertex3f (x+S, y+S, z); glVertex3f (x, y+S, z); glVertex3f (x+S, y, z); glEnd(); } mi->polygon_count++; } /* Draw all the visible glyphs in the strip. */ static void draw_strip (ModeInfo *mi, strip *s) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; int i; for (i = 0; i < GRID_SIZE; i++) { int g = s->glyphs[i]; Bool below_p = (s->spinner_y >= i); if (s->erasing_p) below_p = !below_p; if (g && below_p) /* don't draw cells below the spinner */ { GLfloat brightness; if (!do_waves) brightness = 1.0; else { int j = WAVE_SIZE - ((i + (GRID_SIZE - s->wave_position)) % WAVE_SIZE); brightness = mp->brightness_ramp[j]; } draw_glyph (mi, g, s->highlight[i], s->x, s->y - i, s->z, brightness); } } if (!s->erasing_p) draw_glyph (mi, s->spinner_glyph, False, s->x, s->y - s->spinner_y, s->z, 1.0); } /* qsort comparator for sorting strips by z position */ static int cmp_strips (const void *aa, const void *bb) { const strip *a = *(strip **) aa; const strip *b = *(strip **) bb; return ((int) (a->z * 10000) - (int) (b->z * 10000)); } /* Auto-tracking */ static void auto_track_init (ModeInfo *mi) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; mp->last_view = 0; mp->target_view = 0; mp->view_x = nice_views[mp->last_view].x; mp->view_y = nice_views[mp->last_view].y; mp->view_steps = 100; mp->view_tick = 0; mp->auto_tracking_p = False; } static void auto_track (ModeInfo *mi) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; if (! do_rotate) return; if (mp->button_down_p) return; /* if we're not moving, maybe start moving. Otherwise, do nothing. */ if (! mp->auto_tracking_p) { if (++mp->track_tick < 20/speed) return; mp->track_tick = 0; if (! (random() % 20)) mp->auto_tracking_p = True; else return; } { GLfloat ox = nice_views[mp->last_view].x; GLfloat oy = nice_views[mp->last_view].y; GLfloat tx = nice_views[mp->target_view].x; GLfloat ty = nice_views[mp->target_view].y; /* move from A to B with sinusoidal deltas, so that it doesn't jerk to a stop. */ GLfloat th = sin ((M_PI / 2) * (double) mp->view_tick / mp->view_steps); mp->view_x = (ox + ((tx - ox) * th)); mp->view_y = (oy + ((ty - oy) * th)); mp->view_tick++; if (mp->view_tick >= mp->view_steps) { mp->view_tick = 0; mp->view_steps = (350.0 / speed); mp->last_view = mp->target_view; mp->target_view = (random() % (countof(nice_views) - 1)) + 1; mp->auto_tracking_p = False; } } } /* Window management, etc */ ENTRYPOINT void reshape_matrix (ModeInfo *mi, int width, int height) { GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; if (width > height * 5) { /* tiny window: show middle */ height = width * 9/16; y = -height/2; h = height / (GLfloat) width; } glViewport (0, y, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (80.0, 1/h, 1.0, 100); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool matrix_handle_event (ModeInfo *mi, XEvent *event) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; if (event->xany.type == ButtonPress && event->xbutton.button == Button1) { mp->button_down_p = True; return True; } else if (event->xany.type == ButtonRelease && event->xbutton.button == Button1) { mp->button_down_p = False; return True; } return False; } #if 0 static Bool bigendian (void) { union { int i; char c[sizeof(int)]; } u; u.i = 1; return !u.c[0]; } #endif /* The image with the characters in it is 512x598, meaning that it needs to be copied into a 512x1024 texture. But some machines can't handle textures that large... And it turns out that we aren't using most of the characters in that image anyway, since this program doesn't do anything that makes use of the full range of Latin1 characters. So... this function tosses out the last 32 of the Latin1 characters, resulting in a 512x506 image, which we can then stuff in a 512x512 texture. Voila. If this hack ever grows into something that displays full Latin1 text, well then, Something Else Will Need To Be Done. Since currently GLMatrix does not run textclient / xscreensaver-text, it's not an issue. (XMatrix does that.) */ static void spank_image (matrix_configuration *mp, XImage *xi) { int ch = xi->height / CHAR_ROWS; int cut = 2; unsigned char *bits = (unsigned char *) xi->data; unsigned char *from, *to, *s, *end; int L = xi->bytes_per_line * ch; /* int i;*/ /* Copy row 12 into 10 (which really means, copy 2 into 0, since texture data is upside down.). */ to = bits + (L * cut); from = bits; end = from + L; s = from; while (s < end) *to++ = *s++; /* Then, pull all the bits down by 2 rows. */ to = bits; from = bits + (L * cut); end = bits + (L * CHAR_ROWS); s = from; while (s < end) *to++ = *s++; /* And clear out the rest, for good measure. */ from = bits + (L * (CHAR_ROWS - cut)); end = bits + (L * CHAR_ROWS); s = from; while (s < end) *s++ = 0; xi->height -= (cut * ch); mp->real_char_rows -= cut; # if 0 /* Finally, pull the map indexes back to match the new bits. */ for (i = 0; i < countof(matrix_encoding); i++) if (matrix_encoding[i] > (CHAR_COLS * (CHAR_ROWS - cut))) matrix_encoding[i] -= (cut * CHAR_COLS); # endif } static void load_textures (ModeInfo *mi, Bool flip_p) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; XImage *xi; int x, y; int cw, ch; int orig_w, orig_h; /* The Matrix image is 512x598 -- but GL texture sizes must be powers of 2. So we waste some padding rows to round up. */ xi = image_data_to_ximage (mi->dpy, mi->xgwa.visual, matrix3_png, sizeof(matrix3_png)); orig_w = xi->width; orig_h = xi->height; mp->real_char_rows = CHAR_ROWS; spank_image (mp, xi); if (xi->height != 512 && xi->height != 1024) { xi->height = (xi->height < 512 ? 512 : 1024); xi->data = realloc (xi->data, xi->height * xi->bytes_per_line); if (!xi->data) { fprintf(stderr, "%s: out of memory\n", progname); exit(1); } } if (xi->width != 512) abort(); if (xi->height != 512 && xi->height != 1024) abort(); /* char size in pixels */ cw = orig_w / CHAR_COLS; ch = orig_h / CHAR_ROWS; /* char size in ratio of final (padded) texture size */ mp->tex_char_width = (GLfloat) cw / xi->width; mp->tex_char_height = (GLfloat) ch / xi->height; /* Flip each character's bits horizontally -- we could also just do this by reversing the texture coordinates on the quads, but on some systems that slows things down a lot. */ if (flip_p) { int xx, col; unsigned long buf[100]; for (y = 0; y < xi->height; y++) for (col = 0, xx = 0; col < CHAR_COLS; col++, xx += cw) { for (x = 0; x < cw; x++) buf[x] = XGetPixel (xi, xx+x, y); for (x = 0; x < cw; x++) XPutPixel (xi, xx+x, y, buf[cw-x-1]); } } /* The pixmap is a color image with no transparency. Set the texture's alpha to be the green channel, and set the green channel to be 100%. */ { int rpos, gpos, bpos, apos; /* bitfield positions */ #if 0 /* #### Cherub says that the little-endian case must be taken on MacOSX, or else the colors/alpha are the wrong way around. How can that be the case? */ if (bigendian()) rpos = 24, gpos = 16, bpos = 8, apos = 0; else #endif rpos = 0, gpos = 8, bpos = 16, apos = 24; for (y = 0; y < xi->height; y++) for (x = 0; x < xi->width; x++) { unsigned long p = XGetPixel (xi, x, y); unsigned char r = (p >> rpos) & 0xFF; unsigned char g = (p >> gpos) & 0xFF; unsigned char b = (p >> bpos) & 0xFF; unsigned char a = g; g = 0xFF; p = (r << rpos) | (g << gpos) | (b << bpos) | (a << apos); XPutPixel (xi, x, y, p); } } /* Now load the texture into GL. */ clear_gl_error(); glGenTextures (1, &mp->texture); glPixelStorei (GL_UNPACK_ALIGNMENT, 4); /* messes up -fps */ /* glPixelStorei (GL_UNPACK_ROW_LENGTH, xi->width);*/ glBindTexture (GL_TEXTURE_2D, mp->texture); check_gl_error ("texture init"); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, xi->width, xi->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, xi->data); { char buf[255]; sprintf (buf, "creating %dx%d texture:", xi->width, xi->height); check_gl_error (buf); } glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); /* I'd expect CLAMP to be the thing to do here, but oddly, we get a faint solid green border around the texture if it is *not* REPEAT! */ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); check_gl_error ("texture param"); XDestroyImage (xi); } ENTRYPOINT void init_matrix (ModeInfo *mi) { matrix_configuration *mp; int wire = MI_IS_WIREFRAME(mi); Bool flip_p = 0; int i; if (wire) do_texture = False; MI_INIT (mi, mps); mp = &mps[MI_SCREEN(mi)]; mp->glx_context = init_GL(mi); if (!mode_str || !*mode_str || !strcasecmp(mode_str, "matrix")) { flip_p = 1; mp->glyph_map = matrix_encoding; mp->nglyphs = countof(matrix_encoding); } else if (!strcasecmp (mode_str, "dna")) { flip_p = 0; mp->glyph_map = dna_encoding; mp->nglyphs = countof(dna_encoding); } else if (!strcasecmp (mode_str, "bin") || !strcasecmp (mode_str, "binary")) { flip_p = 0; mp->glyph_map = binary_encoding; mp->nglyphs = countof(binary_encoding); } else if (!strcasecmp (mode_str, "hex") || !strcasecmp (mode_str, "hexadecimal")) { flip_p = 0; mp->glyph_map = hex_encoding; mp->nglyphs = countof(hex_encoding); } else if (!strcasecmp (mode_str, "dec") || !strcasecmp (mode_str, "decimal")) { flip_p = 0; mp->glyph_map = decimal_encoding; mp->nglyphs = countof(decimal_encoding); } else { fprintf (stderr, "%s: `mode' must be matrix, dna, binary, or hex: not `%s'\n", progname, mode_str); exit (1); } reshape_matrix (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); glShadeModel(GL_SMOOTH); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glEnable(GL_NORMALIZE); if (do_texture) load_textures (mi, flip_p); /* to scale coverage-percent to strips, this number looks about right... */ mp->nstrips = (int) (density * 2.2); if (mp->nstrips < 1) mp->nstrips = 1; else if (mp->nstrips > 2000) mp->nstrips = 2000; mp->strips = calloc (mp->nstrips, sizeof(strip)); for (i = 0; i < mp->nstrips; i++) { strip *s = &mp->strips[i]; reset_strip (mi, s); /* If we start all strips from zero at once, then the first few seconds of the animation are much denser than normal. So instead, set all the initial strips to erase-mode with random starting positions. As these die off at random speeds and are re-created, we'll get a more consistent density. */ s->erasing_p = True; s->spinner_y = frand(GRID_SIZE); memset (s->glyphs, 0, sizeof(s->glyphs)); /* no visible glyphs */ } /* Compute the brightness ramp. */ for (i = 0; i < WAVE_SIZE; i++) { GLfloat j = ((WAVE_SIZE - i) / (GLfloat) (WAVE_SIZE - 1)); j *= (M_PI / 2); /* j ranges from 0.0 - PI/2 */ j = sin (j); /* j ranges from 0.0 - 1.0 */ j = 0.2 + (j * 0.8); /* j ranges from 0.2 - 1.0 */ mp->brightness_ramp[i] = j; /* printf("%2d %8.2f\n", i, j); */ } auto_track_init (mi); } #ifdef DEBUG static void draw_grid (ModeInfo *mi) { if (!MI_IS_WIREFRAME(mi)) { glDisable(GL_TEXTURE_2D); glDisable(GL_BLEND); } glPushMatrix(); glColor3f(1, 1, 1); glBegin(GL_LINES); glVertex3f(-GRID_SIZE, 0, 0); glVertex3f(GRID_SIZE, 0, 0); glVertex3f(0, -GRID_SIZE, 0); glVertex3f(0, GRID_SIZE, 0); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, 0); glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, 0); glVertex3f( GRID_SIZE/2, GRID_SIZE/2, 0); glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, 0); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); glEnd(); glBegin(GL_LINES); glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f(-GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); glVertex3f(-GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, GRID_SIZE/2, -GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, -GRID_SIZE/2, GRID_DEPTH/2); glVertex3f( GRID_SIZE/2, GRID_SIZE/2, GRID_DEPTH/2); glEnd(); glPopMatrix(); if (!MI_IS_WIREFRAME(mi)) { glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); } } #endif /* DEBUG */ ENTRYPOINT void draw_matrix (ModeInfo *mi) { matrix_configuration *mp = &mps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int i; if (!mp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context)); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix (); glRotatef(current_device_rotation(), 0, 0, 1); if (do_texture) { glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); /* Jeff Epler points out: By using GL_ONE instead of GL_SRC_ONE_MINUS_ALPHA, glyphs are added to each other, so that a bright glyph with a darker one in front is a little brighter than the bright glyph alone. */ glBlendFunc (GL_SRC_ALPHA, GL_ONE); } if (do_rotate) { glRotatef (mp->view_x, 1, 0, 0); glRotatef (mp->view_y, 0, 1, 0); } #ifdef DEBUG # if 0 glScalef(0.5, 0.5, 0.5); # endif # if 0 glRotatef(-30, 0, 1, 0); # endif draw_grid (mi); #endif mi->polygon_count = 0; /* Render (and tick) each strip, starting at the back (draw the ones farthest from the camera first, to make the alpha transparency work out right.) */ { strip **sorted = malloc (mp->nstrips * sizeof(*sorted)); for (i = 0; i < mp->nstrips; i++) sorted[i] = &mp->strips[i]; qsort (sorted, i, sizeof(*sorted), cmp_strips); for (i = 0; i < mp->nstrips; i++) { strip *s = sorted[i]; tick_strip (mi, s); draw_strip (mi, s); } free (sorted); } auto_track (mi); #if 0 glBegin(GL_QUADS); glColor3f(1,1,1); glTexCoord2f (0,0); glVertex3f(-15,-15,0); glTexCoord2f (0,1); glVertex3f(-15,15,0); glTexCoord2f (1,1); glVertex3f(15,15,0); glTexCoord2f (1,0); glVertex3f(15,-15,0); glEnd(); #endif glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } XSCREENSAVER_MODULE_2 ("GLMatrix", glmatrix, matrix) #endif /* USE_GL */