--- /dev/null
+/* 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 <X11/Xutil.h>
+
+#ifdef HAVE_XSHM_EXTENSION
+#include "xshm.h"
+#endif
+
+#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))
+
+
+
+/* parameters that are user configurable */
+
+/* whether or not to use xshm */
+#ifdef HAVE_XSHM_EXTENSION
+static Bool useShm;
+#endif
+
+/* delay (usec) between iterations */
+static int delay;
+
+/* the maximum number of columns of tiles */
+static int maxColumns;
+
+/* the maximum number of rows of tiles */
+static int maxRows;
+
+/* the size (width and height) of a tile */
+static int tileSize;
+
+/* the width of the border around each tile */
+static int borderWidth;
+
+/* the chance, per iteration, of an interesting event happening */
+static FLOAT eventChance;
+
+/* friction: the fraction (0..1) by which velocity decreased per iteration */
+static FLOAT friction;
+
+/* springiness: the fraction (0..1) of the orientation that turns into
+ * velocity towards the center */
+static FLOAT springiness;
+
+/* transference: the fraction (0..1) of the orientations of orthogonal
+ * neighbors that turns into velocity (in the same direction as the
+ * orientation) */
+static FLOAT transference;
+
+
+
+/* non-user-modifiable immutable definitions */
+
+/* width and height of the window */
+static int windowWidth;
+static int windowHeight;
+
+static Display *display; /* the display to draw on */
+static Window window; /* the window to draw on */
+static Screen *screen; /* the screen to draw on */
+
+static XImage *sourceImage; /* image source of stuff to draw */
+static XImage *workImage; /* work area image, used when rendering */
+static XImage *backgroundImage; /* image filled with background pixels */
+
+static GC backgroundGC; /* GC for the background color */
+static GC foregroundGC; /* GC for the foreground color */
+static 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 */
+
+#ifdef HAVE_XSHM_EXTENSION
+XShmSegmentInfo shmInfo;
+#endif
+
+
+
+/* the model */
+
+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;
+
+static Tile *tiles; /* array of tiles (left->right, top->bottom, row major) */
+static int rows; /* number of rows of tiles */
+static int columns; /* number of columns of tiles */
+
+static Tile **sortedTiles; /* array of tile pointers, sorted by zoom */
+static int tileCount; /* total number of tiles */
+
+#define TILE_AT(col,row) (&tiles[(row) * 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 (XWindowAttributes *xwa)
+{
+ XFillRectangle (display, window, backgroundGC, 0, 0,
+ windowWidth, windowHeight);
+ backgroundImage =
+ XGetImage (display, window, 0, 0, windowWidth, windowHeight,
+ ~0L, ZPixmap);
+
+ grab_screen_image (screen, window);
+ sourceImage = XGetImage (display, window, 0, 0, windowWidth, windowHeight,
+ ~0L, ZPixmap);
+
+#ifdef HAVE_XSHM_EXTENSION
+ workImage = NULL;
+ if (useShm)
+ {
+ workImage = create_xshm_image (display, xwa->visual, xwa->depth,
+ ZPixmap, 0, &shmInfo,
+ windowWidth, windowHeight);
+ if (!workImage)
+ {
+ useShm = False;
+ fprintf (stderr, "create_xshm_image failed\n");
+ }
+ }
+
+ if (workImage == NULL)
+#endif /* HAVE_XSHM_EXTENSION */
+
+ /* just use XSubImage to acquire the right visual, depth, etc;
+ * easier than the other alternatives */
+ workImage = XSubImage (sourceImage, 0, 0, windowWidth, windowHeight);
+}
+
+/* set up the system */
+static void setup (void)
+{
+ XWindowAttributes xgwa;
+ XGCValues gcv;
+
+ XGetWindowAttributes (display, window, &xgwa);
+
+ screen = xgwa.screen;
+ windowWidth = xgwa.width;
+ windowHeight = xgwa.height;
+
+ gcv.line_width = borderWidth;
+ gcv.foreground = get_pixel_resource ("borderColor", "BorderColor",
+ display, xgwa.colormap);
+ borderPixel = gcv.foreground;
+ borderGC = XCreateGC (display, window, GCForeground | GCLineWidth,
+ &gcv);
+
+ gcv.foreground = get_pixel_resource ("background", "Background",
+ display, xgwa.colormap);
+ backgroundPixel = gcv.foreground;
+ backgroundGC = XCreateGC (display, window, GCForeground, &gcv);
+
+ gcv.foreground = get_pixel_resource ("foreground", "Foreground",
+ display, xgwa.colormap);
+ foregroundGC = XCreateGC (display, window, GCForeground, &gcv);
+
+ grabImage (&xgwa);
+}
+
+
+
+/*
+ * the simulation
+ */
+
+/* event: randomize all the angular velocities */
+static void randomizeAllAngularVelocities (void)
+{
+ int c;
+ int r;
+
+ for (r = 0; r < rows; r++)
+ {
+ for (c = 0; c < columns; c++)
+ {
+ TILE_AT (c, r)->vAngle = RAND_VANGLE;
+ }
+ }
+}
+
+/* event: randomize all the zoomular velocities */
+static void randomizeAllZoomularVelocities (void)
+{
+ int c;
+ int r;
+
+ for (r = 0; r < rows; r++)
+ {
+ for (c = 0; c < columns; c++)
+ {
+ TILE_AT (c, r)->vZoom = RAND_VZOOM;
+ }
+ }
+}
+
+/* event: randomize all the velocities */
+static void randomizeAllVelocities (void)
+{
+ randomizeAllAngularVelocities ();
+ randomizeAllZoomularVelocities ();
+}
+
+/* event: randomize all the angular orientations */
+static void randomizeAllAngularOrientations (void)
+{
+ int c;
+ int r;
+
+ for (r = 0; r < rows; r++)
+ {
+ for (c = 0; c < columns; c++)
+ {
+ TILE_AT (c, r)->angle = RAND_ANGLE;
+ }
+ }
+}
+
+/* event: randomize all the zoomular orientations */
+static void randomizeAllZoomularOrientations (void)
+{
+ int c;
+ int r;
+
+ for (r = 0; r < rows; r++)
+ {
+ for (c = 0; c < columns; c++)
+ {
+ TILE_AT (c, r)->zoom = RAND_ZOOM;
+ }
+ }
+}
+
+/* event: randomize all the orientations */
+static void randomizeAllOrientations (void)
+{
+ randomizeAllAngularOrientations ();
+ randomizeAllZoomularOrientations ();
+}
+
+/* event: randomize everything */
+static void randomizeEverything (void)
+{
+ randomizeAllVelocities ();
+ randomizeAllOrientations ();
+}
+
+/* event: pick one tile and randomize all its stats */
+static void randomizeOneTile (void)
+{
+ int c = RAND_FLOAT_01 * columns;
+ int r = RAND_FLOAT_01 * 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 (void)
+{
+ int c;
+ int r = RAND_FLOAT_01 * rows;
+
+ for (c = 0; c < 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 (void)
+{
+ int c = RAND_FLOAT_01 * columns;
+ int r;
+
+ for (r = 0; r < 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 (void)
+{
+ int which;
+
+ if (RAND_FLOAT_01 > eventChance)
+ {
+ return;
+ }
+
+ which = RAND_FLOAT_01 * 10;
+
+ switch (which)
+ {
+ case 0: randomizeAllAngularVelocities (); break;
+ case 1: randomizeAllZoomularVelocities (); break;
+ case 2: randomizeAllVelocities (); break;
+ case 3: randomizeAllAngularOrientations (); break;
+ case 4: randomizeAllZoomularOrientations (); break;
+ case 5: randomizeAllOrientations (); break;
+ case 6: randomizeEverything (); break;
+ case 7: randomizeOneTile (); break;
+ case 8: randomizeOneColumn (); break;
+ case 9: randomizeOneRow (); break;
+ }
+}
+
+/* update the model for one iteration */
+static void updateModel (void)
+{
+ 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 < rows; r++)
+ {
+ for (c = 0; c < 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 * springiness;
+ vz -= t->zoom * springiness;
+
+ if (c > 0)
+ {
+ Tile *t2 = TILE_AT (c - 1, r);
+ va += (t2->angle - a) * transference;
+ vz += (t2->zoom - z) * transference;
+ }
+
+ if (c < (columns - 1))
+ {
+ Tile *t2 = TILE_AT (c + 1, r);
+ va += (t2->angle - a) * transference;
+ vz += (t2->zoom - z) * transference;
+ }
+
+ if (r > 0)
+ {
+ Tile *t2 = TILE_AT (c, r - 1);
+ va += (t2->angle - a) * transference;
+ vz += (t2->zoom - z) * transference;
+ }
+
+ if (r < (rows - 1))
+ {
+ Tile *t2 = TILE_AT (c, r + 1);
+ va += (t2->angle - a) * transference;
+ vz += (t2->zoom - z) * transference;
+ }
+
+ va *= (1.0 - friction);
+ vz *= (1.0 - 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 < rows; r++)
+ {
+ for (c = 0; c < 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 (void)
+{
+ qsort (sortedTiles, tileCount, sizeof (Tile *), sortTilesComparator);
+}
+
+/* render the given tile */
+static void renderTile (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 = (tileSize - borderWidth) / 2.0;
+ FLOAT outerBorder = innerBorder + 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 > windowWidth) maxX = windowWidth;
+ if (minY < 0) minY = 0;
+ if (maxY > windowHeight) maxY = 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 (workImage, x, y, borderPixel);
+ }
+ else
+ {
+ unsigned long p =
+ XGetPixel (sourceImage, srcx + tx, srcy + ty);
+ XPutPixel (workImage, x, y, p);
+ }
+ }
+ }
+}
+
+/* render and display the current model */
+static void renderFrame (void)
+{
+ int n;
+
+ memcpy (workImage->data, backgroundImage->data,
+ workImage->bytes_per_line * workImage->height);
+
+ sortTiles ();
+
+ for (n = 0; n < tileCount; n++)
+ {
+ renderTile (sortedTiles[n]);
+ }
+
+#ifdef HAVE_XSHM_EXTENSION
+ if (useShm)
+ XShmPutImage (display, window, backgroundGC, workImage, 0, 0, 0, 0,
+ windowWidth, windowHeight, False);
+ else
+#endif /* HAVE_XSHM_EXTENSION */
+ XPutImage (display, window, backgroundGC, workImage,
+ 0, 0, 0, 0, windowWidth, windowHeight);
+}
+
+/* set up the model */
+static void setupModel (void)
+{
+ 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 (tileSize > (windowWidth / 2))
+ {
+ tileSize = windowWidth / 2;
+ }
+
+ if (tileSize > (windowHeight / 2))
+ {
+ tileSize = windowHeight / 2;
+ }
+
+ columns = windowWidth / tileSize;
+ rows = windowHeight / tileSize;
+
+ if ((maxColumns != 0) && (columns > maxColumns))
+ {
+ columns = maxColumns;
+ }
+
+ if ((maxRows != 0) && (rows > maxRows))
+ {
+ rows = maxRows;
+ }
+
+ tileCount = rows * columns;
+
+ leftX = (windowWidth - (columns * tileSize) + tileSize) / 2;
+ topY = (windowHeight - (rows * tileSize) + tileSize) / 2;
+
+ tiles = calloc (tileCount, sizeof (Tile));
+ sortedTiles = calloc (tileCount, sizeof (Tile *));
+
+ for (r = 0; r < rows; r++)
+ {
+ for (c = 0; c < columns; c++)
+ {
+ Tile *t = TILE_AT (c, r);
+ t->x = leftX + c * tileSize;
+ t->y = topY + r * tileSize;
+ sortedTiles[c + r * columns] = t;
+ }
+ }
+
+ randomizeEverything ();
+}
+
+/* do one iteration */
+static void oneIteration (void)
+{
+ modelEvents ();
+ updateModel ();
+ renderFrame ();
+}
+
+
+
+/* main and options and stuff */
+
+char *progclass = "Twang";
+
+char *defaults [] = {
+ ".background: black",
+ ".foreground: white",
+ "*borderColor: blue",
+ "*borderWidth: 3",
+ "*delay: 10000",
+ "*eventChance: 0.01",
+ "*friction: 0.05",
+ "*maxColumns: 0",
+ "*maxRows: 0",
+ "*springiness: 0.1",
+ "*tileSize: 120",
+ "*transference: 0.025",
+#ifdef HAVE_XSHM_EXTENSION
+ "*useSHM: True",
+#endif
+ 0
+};
+
+XrmOptionDescRec options [] = {
+ { "-border-color", ".borderColor", XrmoptionSepArg, 0 },
+ { "-border-width", ".borderWidth", XrmoptionSepArg, 0 },
+ { "-delay", ".delay", 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 },
+#ifdef HAVE_XSHM_EXTENSION
+ { "-shm", ".useSHM", XrmoptionNoArg, "True" },
+ { "-no-shm", ".useSHM", XrmoptionNoArg, "False" },
+#endif
+ { 0, 0, 0, 0 }
+};
+
+/* initialize the user-specifiable params */
+static void initParams (void)
+{
+ int problems = 0;
+
+ borderWidth = get_integer_resource ("borderWidth", "Integer");
+ if (borderWidth < 0)
+ {
+ fprintf (stderr, "error: border width must be at least 0\n");
+ problems = 1;
+ }
+
+ delay = get_integer_resource ("delay", "Delay");
+ if (delay < 0)
+ {
+ fprintf (stderr, "error: delay must be at least 0\n");
+ problems = 1;
+ }
+
+ eventChance = get_float_resource ("eventChance", "Double");
+ if ((eventChance < 0.0) || (eventChance > 1.0))
+ {
+ fprintf (stderr, "error: eventChance must be in the range 0..1\n");
+ problems = 1;
+ }
+
+ friction = get_float_resource ("friction", "Double");
+ if ((friction < 0.0) || (friction > 1.0))
+ {
+ fprintf (stderr, "error: friction must be in the range 0..1\n");
+ problems = 1;
+ }
+
+ maxColumns = get_integer_resource ("maxColumns", "Integer");
+ if (maxColumns < 0)
+ {
+ fprintf (stderr, "error: max columns must be at least 0\n");
+ problems = 1;
+ }
+
+ maxRows = get_integer_resource ("maxRows", "Integer");
+ if (maxRows < 0)
+ {
+ fprintf (stderr, "error: max rows must be at least 0\n");
+ problems = 1;
+ }
+
+ springiness = get_float_resource ("springiness", "Double");
+ if ((springiness < 0.0) || (springiness > 1.0))
+ {
+ fprintf (stderr, "error: springiness must be in the range 0..1\n");
+ problems = 1;
+ }
+
+ tileSize = get_integer_resource ("tileSize", "Integer");
+ if (tileSize < 1)
+ {
+ fprintf (stderr, "error: tile size must be at least 1\n");
+ problems = 1;
+ }
+
+ transference = get_float_resource ("transference", "Double");
+ if ((transference < 0.0) || (transference > 1.0))
+ {
+ fprintf (stderr, "error: transference must be in the range 0..1\n");
+ problems = 1;
+ }
+
+#ifdef HAVE_XSHM_EXTENSION
+ useShm = get_boolean_resource ("useSHM", "Boolean");
+#endif
+
+ if (problems)
+ {
+ exit (1);
+ }
+}
+
+/* main function */
+void screenhack (Display *dpy, Window win)
+{
+ display = dpy;
+ window = win;
+
+ initParams ();
+ setup ();
+ setupModel ();
+
+ for (;;)
+ {
+ oneIteration ();
+ XSync (dpy, False);
+ screenhack_handle_events (dpy);
+ usleep (delay);
+ }
+}