1063 lines
29 KiB
C
1063 lines
29 KiB
C
/* glmatrix, Copyright (c) 2003, 2004 Jamie Zawinski <jwz@jwz.org>
|
|
*
|
|
* 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 refresh_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 "wscreensaver-glue.h"
|
|
|
|
#ifdef __GNUC__
|
|
__extension__ /* don't warn about "string length is greater than the length
|
|
ISO C89 compilers are required to support" when including
|
|
the following XPM file... */
|
|
#endif
|
|
#include "matrix3.xpm"
|
|
|
|
|
|
#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 = 1.0;
|
|
static GLfloat density = 20.0;
|
|
static Bool do_clock;
|
|
static char *timefmt;
|
|
static Bool do_fog = 1;
|
|
static Bool do_waves;
|
|
static Bool do_rotate = 1;
|
|
static Bool do_texture = 1;
|
|
static char *mode_str;
|
|
|
|
#if 0
|
|
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};
|
|
#endif
|
|
|
|
/* 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))
|
|
{
|
|
unsigned 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;
|
|
|
|
glViewport (0, 0, (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);
|
|
}
|
|
|
|
|
|
#if 0
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
#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.
|
|
*/
|
|
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 XPM is 512x598 -- but GL texture sizes must be powers of 2.
|
|
So we waste some padding rows to round up.
|
|
*/
|
|
xi = xpm_to_ximage (matrix3_xpm);
|
|
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);
|
|
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_INT_8_8_8_8_REV, 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;
|
|
|
|
if (!mps) {
|
|
mps = (matrix_configuration *)
|
|
calloc (MI_NUM_SCREENS(mi), sizeof (matrix_configuration));
|
|
if (!mps) {
|
|
fprintf(stderr, "%s: out of memory\n", progname);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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);
|
|
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);
|
|
}
|
|
|
|
/* 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)];
|
|
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 ();
|
|
|
|
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(MI_DISPLAY(mi), MI_WINDOW(mi));
|
|
}
|
|
|
|
WL_EXPORT struct wscreensaver_plugin glmatrix_screensaver = {
|
|
"GLMatrix",
|
|
init_matrix,
|
|
draw_matrix,
|
|
reshape_matrix
|
|
};
|