/* twang, twist around screen bits, v1.3
* by Dan Bornstein, danfuzz@milk.com
* Copyright (c) 2003 Dan Bornstein. All rights reserved.
*
* 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.
*
* See the included man page for more details.
*/
#include <math.h>
#include "screenhack.h"
#include "xshm.h"
#define FLOAT double
/* random float in the range (-1..1) */
#define RAND_FLOAT_PM1 \
(((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
/* random float in the range (0..1) */
#define RAND_FLOAT_01 \
(((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
typedef struct
{
int x; /* x coordinate of the center of the tile */
int y; /* y coordinate of the center of the tile */
FLOAT angle; /* angle of the tile (-pi..pi) */
FLOAT zoom; /* log of the zoom of the tile (-1..1) */
FLOAT vAngle; /* angular velocity (-pi/4..pi/4) */
FLOAT vZoom; /* zoomular velocity (-0.25..0.25) */
}
Tile;
struct state {
Display *dpy;
Window window;
int delay; /* delay (usec) between iterations */
int duration; /* time (sec) before loading new image */
int maxColumns; /* the maximum number of columns of tiles */
int maxRows; /* the maximum number of rows of tiles */
int tileSize; /* the size (width and height) of a tile */
int borderWidth; /* the width of the border around each tile */
FLOAT eventChance; /* the chance, per iteration, of an interesting event happening */
FLOAT friction; /* friction: the fraction (0..1) by which velocity decreased per iteration */
FLOAT springiness; /* springiness: the fraction (0..1) of the orientation that turns into velocity towards the center */
FLOAT transference; /* transference: the fraction (0..1) of the orientations of orthogonal neighbors that turns into velocity (in the same direction as the orientation) */
int windowWidth; /* width and height of the window */
int windowHeight;
Screen *screen; /* the screen to draw on */
XImage *sourceImage; /* image source of stuff to draw */
XImage *workImage; /* work area image, used when rendering */
GC backgroundGC; /* GC for the background color */
GC foregroundGC; /* GC for the foreground color */
GC borderGC; /* GC for the border color */
unsigned long backgroundPixel; /* background color as a pixel value */
unsigned long borderPixel; /* border color as a pixel value */
Tile *tiles; /* array of tiles (left->right, top->bottom, row major) */
int rows; /* number of rows of tiles */
int columns; /* number of columns of tiles */
Tile **sortedTiles; /* array of tile pointers, sorted by zoom */
int tileCount; /* total number of tiles */
time_t start_time;
async_load_state *img_loader;
Pixmap pm;
XShmSegmentInfo shmInfo;
};
#define TILE_AT(col,row) (&st->tiles[(row) * st->columns + (col)])
#define MAX_VANGLE (M_PI / 4.0)
#define MAX_VZOOM 0.25
#define RAND_ANGLE (RAND_FLOAT_PM1 * M_PI)
#define RAND_ZOOM (RAND_FLOAT_PM1)
#define RAND_VANGLE (RAND_FLOAT_PM1 * MAX_VANGLE)
#define RAND_VZOOM (RAND_FLOAT_PM1 * MAX_VZOOM)
/*
* overall setup stuff
*/
/* grab the source image */
static void
grabImage_start (struct state *st, XWindowAttributes *xwa)
{
/* On MacOS X11, XGetImage on a Window often gets an inexplicable BadMatch,
possibly due to the window manager having occluded something? It seems
nondeterministic. Loading the image into a pixmap instead fixes it. */
if (st->pm) XFreePixmap (st->dpy, st->pm);
st->pm = XCreatePixmap (st->dpy, st->window,
xwa->width, xwa->height, xwa->depth);
st->start_time = time ((time_t *) 0);
st->img_loader = load_image_async_simple (0, xwa->screen, st->window,
st->pm, 0, 0);
}
static void
grabImage_done (struct state *st)
{
XWindowAttributes xwa;
XGetWindowAttributes (st->dpy, st->window, &xwa);
st->start_time = time ((time_t *) 0);
if (st->sourceImage) XDestroyImage (st->sourceImage);
st->sourceImage = XGetImage (st->dpy, st->pm, 0, 0,
st->windowWidth, st->windowHeight,
~0L, ZPixmap);
if (st->workImage) destroy_xshm_image (st->dpy, st->workImage,
&st->shmInfo);
st->workImage = create_xshm_image (st->dpy, xwa.visual, xwa.depth,
ZPixmap, &st->shmInfo,
st->windowWidth, st->windowHeight);
}
/* set up the system */
static void setup (struct state *st)
{
XWindowAttributes xgwa;
XGCValues gcv;
XGetWindowAttributes (st->dpy, st->window, &xgwa);
st->screen = xgwa.screen;
st->windowWidth = xgwa.width;
st->windowHeight = xgwa.height;
gcv.line_width = st->borderWidth;
gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
"borderColor", "BorderColor");
st->borderPixel = gcv.foreground;
st->borderGC = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth,
&gcv);
gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
"background", "Background");
st->backgroundPixel = gcv.foreground;
st->backgroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
gcv.foreground = get_pixel_resource (st->dpy, xgwa.colormap,
"foreground", "Foreground");
st->foregroundGC = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
grabImage_start (st, &xgwa);
}
/*
* the simulation
*/
/* event: randomize all the angular velocities */
static void randomizeAllAngularVelocities (struct state *st)
{
int c;
int r;
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
TILE_AT (c, r)->vAngle = RAND_VANGLE;
}
}
}
/* event: randomize all the zoomular velocities */
static void randomizeAllZoomularVelocities (struct state *st)
{
int c;
int r;
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
TILE_AT (c, r)->vZoom = RAND_VZOOM;
}
}
}
/* event: randomize all the velocities */
static void randomizeAllVelocities (struct state *st)
{
randomizeAllAngularVelocities (st);
randomizeAllZoomularVelocities (st);
}
/* event: randomize all the angular orientations */
static void randomizeAllAngularOrientations (struct state *st)
{
int c;
int r;
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
TILE_AT (c, r)->angle = RAND_ANGLE;
}
}
}
/* event: randomize all the zoomular orientations */
static void randomizeAllZoomularOrientations (struct state *st)
{
int c;
int r;
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
TILE_AT (c, r)->zoom = RAND_ZOOM;
}
}
}
/* event: randomize all the orientations */
static void randomizeAllOrientations (struct state *st)
{
randomizeAllAngularOrientations (st);
randomizeAllZoomularOrientations (st);
}
/* event: randomize everything */
static void randomizeEverything (struct state *st)
{
randomizeAllVelocities (st);
randomizeAllOrientations (st);
}
/* event: pick one tile and randomize all its stats */
static void randomizeOneTile (struct state *st)
{
int c = RAND_FLOAT_01 * st->columns;
int r = RAND_FLOAT_01 * st->rows;
Tile *t = TILE_AT (c, r);
t->angle = RAND_ANGLE;
t->zoom = RAND_ZOOM;
t->vAngle = RAND_VANGLE;
t->vZoom = RAND_VZOOM;
}
/* event: pick one row and randomize everything about each of its tiles */
static void randomizeOneRow (struct state *st)
{
int c;
int r = RAND_FLOAT_01 * st->rows;
for (c = 0; c < st->columns; c++)
{
Tile *t = TILE_AT (c, r);
t->angle = RAND_ANGLE;
t->zoom = RAND_ZOOM;
t->vAngle = RAND_VANGLE;
t->vZoom = RAND_VZOOM;
}
}
/* event: pick one column and randomize everything about each of its tiles */
static void randomizeOneColumn (struct state *st)
{
int c = RAND_FLOAT_01 * st->columns;
int r;
for (r = 0; r < st->rows; r++)
{
Tile *t = TILE_AT (c, r);
t->angle = RAND_ANGLE;
t->zoom = RAND_ZOOM;
t->vAngle = RAND_VANGLE;
t->vZoom = RAND_VZOOM;
}
}
/* do model event processing */
static void modelEvents (struct state *st)
{
int which;
if (RAND_FLOAT_01 > st->eventChance)
{
return;
}
which = RAND_FLOAT_01 * 10;
switch (which)
{
case 0: randomizeAllAngularVelocities (st); break;
case 1: randomizeAllZoomularVelocities (st); break;
case 2: randomizeAllVelocities (st); break;
case 3: randomizeAllAngularOrientations (st); break;
case 4: randomizeAllZoomularOrientations (st); break;
case 5: randomizeAllOrientations (st); break;
case 6: randomizeEverything (st); break;
case 7: randomizeOneTile (st); break;
case 8: randomizeOneColumn (st); break;
case 9: randomizeOneRow (st); break;
}
}
/* update the model for one iteration */
static void updateModel (struct state *st)
{
int r;
int c;
/* for each tile, decrease its velocities according to the friction,
* and increase them based on its current orientation and the orientations
* of its orthogonal neighbors */
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
Tile *t = TILE_AT (c, r);
FLOAT a = t->angle;
FLOAT z = t->zoom;
FLOAT va = t->vAngle;
FLOAT vz = t->vZoom;
va -= t->angle * st->springiness;
vz -= t->zoom * st->springiness;
if (c > 0)
{
Tile *t2 = TILE_AT (c - 1, r);
va += (t2->angle - a) * st->transference;
vz += (t2->zoom - z) * st->transference;
}
if (c < (st->columns - 1))
{
Tile *t2 = TILE_AT (c + 1, r);
va += (t2->angle - a) * st->transference;
vz += (t2->zoom - z) * st->transference;
}
if (r > 0)
{
Tile *t2 = TILE_AT (c, r - 1);
va += (t2->angle - a) * st->transference;
vz += (t2->zoom - z) * st->transference;
}
if (r < (st->rows - 1))
{
Tile *t2 = TILE_AT (c, r + 1);
va += (t2->angle - a) * st->transference;
vz += (t2->zoom - z) * st->transference;
}
va *= (1.0 - st->friction);
vz *= (1.0 - st->friction);
if (va > MAX_VANGLE) va = MAX_VANGLE;
else if (va < -MAX_VANGLE) va = -MAX_VANGLE;
t->vAngle = va;
if (vz > MAX_VZOOM) vz = MAX_VZOOM;
else if (vz < -MAX_VZOOM) vz = -MAX_VZOOM;
t->vZoom = vz;
}
}
/* for each tile, update its orientation based on its velocities */
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
Tile *t = TILE_AT (c, r);
FLOAT a = t->angle + t->vAngle;
FLOAT z = t->zoom + t->vZoom;
if (a > M_PI) a = M_PI;
else if (a < -M_PI) a = -M_PI;
t->angle = a;
if (z > 1.0) z = 1.0;
else if (z < -1.0) z = -1.0;
t->zoom = z;
}
}
}
/* the comparator to us to sort the tiles (used immediately below); it'd
* sure be nice if C allowed inner functions (or jeebus-forbid *real
* closures*!) */
static int sortTilesComparator (const void *v1, const void *v2)
{
Tile *t1 = *(Tile **) v1;
Tile *t2 = *(Tile **) v2;
if (t1->zoom < t2->zoom)
{
return -1;
}
if (t1->zoom > t2->zoom)
{
return 1;
}
return 0;
}
/* sort the tiles in sortedTiles by zoom */
static void sortTiles (struct state *st)
{
qsort (st->sortedTiles, st->tileCount, sizeof (Tile *), sortTilesComparator);
}
/* render the given tile */
static void renderTile (struct state *st, Tile *t)
{
/* note: the zoom as stored per tile is log-based (centered on 0, with
* 0 being no zoom, but the range for zoom-as-drawn is 0.4..2.5,
* hence the alteration of t->zoom, below */
int x, y;
int tx = t->x;
int ty = t->y;
FLOAT zoom = pow (2.5, t->zoom);
FLOAT ang = -t->angle;
FLOAT sinAng = sin (ang);
FLOAT cosAng = cos (ang);
FLOAT innerBorder = (st->tileSize - st->borderWidth) / 2.0;
FLOAT outerBorder = innerBorder + st->borderWidth;
int maxCoord = outerBorder * zoom * (fabs (sinAng) + fabs (cosAng));
int minX = tx - maxCoord;
int maxX = tx + maxCoord;
int minY = ty - maxCoord;
int maxY = ty + maxCoord;
FLOAT prey;
if (minX < 0) minX = 0;
if (maxX > st->windowWidth) maxX = st->windowWidth;
if (minY < 0) minY = 0;
if (maxY > st->windowHeight) maxY = st->windowHeight;
sinAng /= zoom;
cosAng /= zoom;
for (y = minY, prey = y - ty; y < maxY; y++, prey++)
{
FLOAT prex = minX - tx;
FLOAT srcx = prex * cosAng - prey * sinAng;
FLOAT srcy = prex * sinAng + prey * cosAng;
for (x = minX;
x < maxX;
x++, srcx += cosAng, srcy += sinAng)
{
if ((srcx < -innerBorder) || (srcx >= innerBorder) ||
(srcy < -innerBorder) || (srcy >= innerBorder))
{
if ((srcx < -outerBorder) || (srcx >= outerBorder) ||
(srcy < -outerBorder) || (srcy >= outerBorder))
{
continue;
}
XPutPixel (st->workImage, x, y, st->borderPixel);
}
else
{
unsigned long p =
XGetPixel (st->sourceImage, srcx + tx, srcy + ty);
XPutPixel (st->workImage, x, y, p);
}
}
}
}
/* render and display the current model */
static void renderFrame (struct state *st)
{
int n;
/* This assumes black is zero. */
memset (st->workImage->data, 0,
st->workImage->bytes_per_line * st->workImage->height);
sortTiles (st);
for (n = 0; n < st->tileCount; n++)
{
renderTile (st, st->sortedTiles[n]);
}
put_xshm_image (st->dpy, st->window, st->backgroundGC, st->workImage, 0, 0, 0, 0,
st->windowWidth, st->windowHeight, &st->shmInfo);
}
/* set up the model */
static void setupModel (struct state *st)
{
int c;
int r;
int leftX; /* x of the center of the top-left tile */
int topY; /* y of the center of the top-left tile */
if (st->tileSize > (st->windowWidth / 2))
{
st->tileSize = st->windowWidth / 2;
}
if (st->tileSize > (st->windowHeight / 2))
{
st->tileSize = st->windowHeight / 2;
}
st->columns = st->tileSize ? st->windowWidth / st->tileSize : 0;
st->rows = st->tileSize ? st->windowHeight / st->tileSize : 0;
if ((st->maxColumns != 0) && (st->columns > st->maxColumns))
{
st->columns = st->maxColumns;
}
if ((st->maxRows != 0) && (st->rows > st->maxRows))
{
st->rows = st->maxRows;
}
st->tileCount = st->rows * st->columns;
leftX = (st->windowWidth - (st->columns * st->tileSize) + st->tileSize) / 2;
topY = (st->windowHeight - (st->rows * st->tileSize) + st->tileSize) / 2;
if (st->tileCount < 1) st->tileCount = 1;
st->tiles = calloc (st->tileCount, sizeof (Tile));
st->sortedTiles = calloc (st->tileCount, sizeof (Tile *));
for (r = 0; r < st->rows; r++)
{
for (c = 0; c < st->columns; c++)
{
Tile *t = TILE_AT (c, r);
t->x = leftX + c * st->tileSize;
t->y = topY + r * st->tileSize;
st->sortedTiles[c + r * st->columns] = t;
}
}
randomizeEverything (st);
}
/* do one iteration */
static unsigned long
twang_draw (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
if (st->img_loader) /* still loading */
{
st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
if (! st->img_loader) { /* just finished */
grabImage_done (st);
}
return st->delay;
}
if (!st->img_loader &&
st->start_time + st->duration < time ((time_t *) 0)) {
XWindowAttributes xgwa;
XGetWindowAttributes (st->dpy, st->window, &xgwa);
grabImage_start (st, &xgwa);
return st->delay;
}
modelEvents (st);
updateModel (st);
renderFrame (st);
return st->delay;
}
static void
twang_reshape (Display *dpy, Window window, void *closure,
unsigned int w, unsigned int h)
{
}
static Bool
twang_event (Display *dpy, Window window, void *closure, XEvent *event)
{
struct state *st = (struct state *) closure;
if (screenhack_event_helper (dpy, window, event))
{
st->start_time = 0;
return True;
}
return False;
}
static void
twang_free (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
if (st->pm) XFreePixmap (dpy, st->pm);
free (st);
}
/* main and options and stuff */
/* initialize the user-specifiable params */
static void initParams (struct state *st)
{
int problems = 0;
st->borderWidth = get_integer_resource (st->dpy, "borderWidth", "Integer");
if (st->borderWidth < 0)
{
fprintf (stderr, "error: border width must be at least 0\n");
problems = 1;
}
st->delay = get_integer_resource (st->dpy, "delay", "Delay");
if (st->delay < 0)
{
fprintf (stderr, "error: delay must be at least 0\n");
problems = 1;
}
st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
if (st->duration < 1) st->duration = 1;
st->eventChance = get_float_resource (st->dpy, "eventChance", "Double");
if ((st->eventChance < 0.0) || (st->eventChance > 1.0))
{
fprintf (stderr, "error: eventChance must be in the range 0..1\n");
problems = 1;
}
st->friction = get_float_resource (st->dpy, "friction", "Double");
if ((st->friction < 0.0) || (st->friction > 1.0))
{
fprintf (stderr, "error: friction must be in the range 0..1\n");
problems = 1;
}
st->maxColumns = get_integer_resource (st->dpy, "maxColumns", "Integer");
if (st->maxColumns < 0)
{
fprintf (stderr, "error: max columns must be at least 0\n");
problems = 1;
}
st->maxRows = get_integer_resource (st->dpy, "maxRows", "Integer");
if (st->maxRows < 0)
{
fprintf (stderr, "error: max rows must be at least 0\n");
problems = 1;
}
st->springiness = get_float_resource (st->dpy, "springiness", "Double");
if ((st->springiness < 0.0) || (st->springiness > 1.0))
{
fprintf (stderr, "error: springiness must be in the range 0..1\n");
problems = 1;
}
st->tileSize = get_integer_resource (st->dpy, "tileSize", "Integer");
if (st->tileSize < 1)
{
fprintf (stderr, "error: tile size must be at least 1\n");
problems = 1;
}
st->transference = get_float_resource (st->dpy, "transference", "Double");
if ((st->transference < 0.0) || (st->transference > 1.0))
{
fprintf (stderr, "error: transference must be in the range 0..1\n");
problems = 1;
}
if (problems)
{
exit (1);
}
}
static void *
twang_init (Display *dpy, Window win)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
st->dpy = dpy;
st->window = win;
initParams (st);
setup (st);
setupModel (st);
return st;
}
static const char *twang_defaults [] = {
".background: black",
".foreground: white",
".lowrez: true",
"*borderColor: blue",
"*borderWidth: 3",
"*delay: 10000",
"*duration: 120",
"*eventChance: 0.01",
"*friction: 0.05",
"*maxColumns: 0",
"*maxRows: 0",
"*springiness: 0.1",
"*tileSize: 120",
"*transference: 0.025",
#ifdef HAVE_XSHM_EXTENSION
"*useSHM: True",
#else
"*useSHM: False",
#endif
#ifdef HAVE_MOBILE
"*ignoreRotation: True",
"*rotateImages: True",
#endif
0
};
static XrmOptionDescRec twang_options [] = {
{ "-border-color", ".borderColor", XrmoptionSepArg, 0 },
{ "-border-width", ".borderWidth", XrmoptionSepArg, 0 },
{ "-delay", ".delay", XrmoptionSepArg, 0 },
{ "-duration", ".duration", XrmoptionSepArg, 0 },
{ "-event-chance", ".eventChance", XrmoptionSepArg, 0 },
{ "-friction", ".friction", XrmoptionSepArg, 0 },
{ "-max-columns", ".maxColumns", XrmoptionSepArg, 0 },
{ "-max-rows", ".maxRows", XrmoptionSepArg, 0 },
{ "-springiness", ".springiness", XrmoptionSepArg, 0 },
{ "-tile-size", ".tileSize", XrmoptionSepArg, 0 },
{ "-transference", ".transference", XrmoptionSepArg, 0 },
{ "-shm", ".useSHM", XrmoptionNoArg, "True" },
{ "-no-shm", ".useSHM", XrmoptionNoArg, "False" },
{ 0, 0, 0, 0 }
};
XSCREENSAVER_MODULE ("Twang", twang)