From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / nerverot.c
index dcacb5ae097d24ef1c0a2f1c5023f41bee8cd0ce..fe58ea1a5d1779c8b02157e357904ef1fa19277e 100644 (file)
@@ -1,6 +1,6 @@
-/* nerverot, nervous rotation of random thingies, v1.0
+/* nerverot, nervous rotation of random thingies, v1.4
  * by Dan Bornstein, danfuzz@milk.com
- * Copyright (c) 2000 Dan Bornstein.
+ * Copyright (c) 2000-2001 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 goal of this screensaver is to be interesting and compelling to
  * watch, yet induce a state of nervous edginess in the viewer.
  *
- * Brief description of options/resources:
- *
- *   -fg <color>: foreground color
- *   -bg <color>: background color
- *   -delay <usec>: delay between frames
- *   -event-chance <frac>: chance, per iteration, that an interesting event
- *     will happen (range 0..1)
- *   -iter-amt <frac>: amount, per iteration, to move towards rotation and
- *     scale targets (range 0..1)
- *   -count <n>: number of blots
- *   -colors <n>: number of colors to use
- *   -lineWidth <n>: width of lines (0 means an optimized thin line)
- *   -nervousness <frac>: amount of nervousness (range 0..1)
- *   -min-scale <frac>: minimum scale of drawing as fraction of base scale
- *     (which is the minumum of the width or height of the screen) (range
- *     0..10)
- *   -max-scale <frac>: maximum scale of drawing as fraction of base scale
- *     (which is the minumum of the width or height of the screen) (range
- *     0..10)
- *   -min-radius <n>: minimum radius for drawing blots (range 1..100)
- *   -max-radius <n>: maximum radius for drawing blots (range 1..100)
- *   -max-nerve-radius <frac>: maximum nervousness radius (range 0..1)
+ * See the included man page for more details.
  */
 
 #include <math.h>
 
 /* random float in the range (-1..1) */
 #define RAND_FLOAT_PM1 \
-        (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
+        (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
 
 /* random float in the range (0..1) */
 #define RAND_FLOAT_01 \
-        (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000))
-
-
-
-/* parameters that are user configurable */
-
-/* number of blots */
-static int requestedBlotCount;
-
-/* delay (usec) between iterations */
-int delay;
-
-/* variability of xoff/yoff per iteration (0..1) */
-static FLOAT nervousness;
-
-/* max nervousness radius (0..1) */
-static FLOAT maxNerveRadius;
-
-/* chance per iteration that an event will happen */
-static FLOAT eventChance;
-
-/* fraction (0..1) towards rotation target or scale target to move each
- * iteration */
-static FLOAT iterAmt;
-
-/* min and max scale for drawing, as fraction of baseScale */
-static FLOAT minScale;
-static FLOAT maxScale;
-
-/* min and max radius of blot drawing */
-static int minRadius;
-static int maxRadius;
-
-/* the number of colors to use */
-static int colorCount;
-
-/* width of lines */
-static int lineWidth;
-
-
-
-/* non-user-modifiable immutable definitions */
-
-/* base scale factor for drawing, calculated as
- * max(screenWidth,screenHeight) */
-static int baseScale;
-
-/* width and height of the window */
-static int windowWidth;
-static int windowHeight;
-
-/* center position of the window */
-static int centerX;
-static int centerY;
-
-static Display *display; /* the display to draw on */
-static Window window;    /* the window to draw on */
-static GC *gcs;          /* array of gcs, one per color used */
-
+        (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
 
 
 /* structure of the model */
@@ -131,52 +52,87 @@ typedef struct linesegment_s
     int y2;
 } LineSegment;
 
-/* array of the blots in the model */
-static Blot *blots = NULL;
-static int blotCount;
-
 /* each blot draws as a simple 2d shape with each coordinate as an int
  * in the range (-1..1); this is the base shape */
-static XPoint blotShape[] = { { 0, 0}, { 1, 0}, { 1, 1}, 
-                             { 0, 1}, {-1, 1}, {-1, 0}, 
-                             {-1,-1}, { 0,-1}, { 1,-1} };
+static const XPoint blotShape[] = { { 0, 0}, { 1, 0}, { 1, 1}, 
+                                    { 0, 1}, {-1, 1}, {-1, 0}, 
+                                    {-1,-1}, { 0,-1}, { 1,-1} };
 static int blotShapeCount = sizeof (blotShape) / sizeof (XPoint);
 
-/* two arrays of line segments; one for the ones to erase, and one for the
- * ones to draw */
-static int segCount;
-static LineSegment *segsToDraw = NULL;
-static LineSegment *segsToErase = NULL;
 
-/* current rotation values per axis, scale factor, and light position */
-static FLOAT xRot;
-static FLOAT yRot;
-static FLOAT zRot;
-static FLOAT curScale;
-static FLOAT lightX;
-static FLOAT lightY;
-static FLOAT lightZ;
 
-/* target rotation values per axis, scale factor, and light position */
-static FLOAT xRotTarget;
-static FLOAT yRotTarget;
-static FLOAT zRotTarget;
-static FLOAT scaleTarget;
-static FLOAT lightXTarget;
-static FLOAT lightYTarget;
-static FLOAT lightZTarget;
 
-/* current absolute offsets from the center */
-static int centerXOff = 0;
-static int centerYOff = 0;
 
-/* iterations until the model changes */
-static int itersTillNext;
+struct state {
+  Display *dpy;
+  Window window;
+
+   int requestedBlotCount;     /* number of blots */
+   int delay;          /* delay (usec) between iterations */
+   int maxIters;               /* max iterations per model */
+   FLOAT nervousness;  /* variability of xoff/yoff per iteration (0..1) */
+   FLOAT maxNerveRadius;       /* max nervousness radius (0..1) */
+   FLOAT eventChance;  /* chance per iteration that an event will happen */
+   FLOAT iterAmt;              /* fraction (0..1) towards rotation target or scale target to move each * iteration */
+   FLOAT minScale;             /* min and max scale for drawing, as fraction of baseScale */
+   FLOAT maxScale;
+   int minRadius;              /* min and max radius of blot drawing */
+   int maxRadius;
+   int colorCount;             /* the number of colors to use */
+
+   int lineWidth;              /* width of lines */
+
+   Bool doubleBuffer;  /* whether or not to do double-buffering */
+
+
+   int baseScale;              /* base scale factor for drawing, calculated as max(screenWidth,screenHeight) */
+
+
+   int windowWidth;            /* width and height of the window */
+   int windowHeight;
+
+   int centerX;                /* center position of the window */
+   int centerY;
+
+   Drawable drawable; /* the thing to directly draw on */
+   GC *gcs;           /* array of gcs, one per color used */
+
+   Blot *blots;        /* array of the blots in the model */
+   int blotCount;
+
+   int segCount;               /* two arrays of line segments; one for the ones to erase, and one for the ones to draw */
+
+   LineSegment *segsToDraw;
+   LineSegment *segsToErase;
+
+
+   FLOAT xRot;         /* current rotation values per axis, scale factor, and light position */
 
+   FLOAT yRot;
+   FLOAT zRot;
+   FLOAT curScale;
+   FLOAT lightX;
+   FLOAT lightY;
+   FLOAT lightZ;
+
+   FLOAT xRotTarget;   /* target rotation values per axis, scale factor, and light position */
+
+   FLOAT yRotTarget;
+   FLOAT zRotTarget;
+   FLOAT scaleTarget;
+   FLOAT lightXTarget;
+   FLOAT lightYTarget;
+   FLOAT lightZTarget;
+
+   int centerXOff;     /* current absolute offsets from the center */
+   int centerYOff;
+
+   int itersTillNext;  /* iterations until the model changes */
+};
 
 
 /*
- * blot setup stuff
+ * generic blot setup and manipulation
  */
 
 /* initialize a blot with the given coordinates and random display offsets */
@@ -199,17 +155,17 @@ static void initBlot (Blot *b, FLOAT x, FLOAT y, FLOAT z)
 }
 
 /* scale the blots to have a max distance of 1 from the center */
-static void scaleBlotsToRadius1 (void)
+static void scaleBlotsToRadius1 (struct state *st)
 {
     FLOAT max = 0.0;
     int n;
 
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
        FLOAT distSquare = 
-           blots[n].x * blots[n].x +
-           blots[n].y * blots[n].y +
-           blots[n].z * blots[n].z;
+           st->blots[n].x * st->blots[n].x +
+           st->blots[n].y * st->blots[n].y +
+           st->blots[n].z * st->blots[n].z;
        if (distSquare > max)
        {
            max = distSquare;
@@ -223,37 +179,89 @@ static void scaleBlotsToRadius1 (void)
 
     max = sqrt (max);
 
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
-       blots[n].x /= max;
-       blots[n].y /= max;
-       blots[n].z /= max;
+       st->blots[n].x /= max;
+       st->blots[n].y /= max;
+       st->blots[n].z /= max;
     }
 }
 
 /* randomly reorder the blots */
-static void randomlyReorderBlots (void)
+static void randomlyReorderBlots (struct state *st)
 {
     int n;
 
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
-       int m = RAND_FLOAT_01 * (blotCount - n) + n;
-       Blot tmpBlot = blots[n];
-       blots[n] = blots[m];
-       blots[m] = tmpBlot;
+       int m = RAND_FLOAT_01 * (st->blotCount - n) + n;
+       Blot tmpBlot = st->blots[n];
+       st->blots[n] = st->blots[m];
+       st->blots[m] = tmpBlot;
     }
 }
 
+/* randomly rotate the blots around the origin */
+static void randomlyRotateBlots (struct state *st)
+{
+    int n;
+
+    /* random amounts to rotate about each axis */
+    FLOAT xRot = RAND_FLOAT_PM1 * M_PI;
+    FLOAT yRot = RAND_FLOAT_PM1 * M_PI;
+    FLOAT zRot = RAND_FLOAT_PM1 * M_PI;
+
+    /* rotation factors */
+    FLOAT sinX = sin (xRot);
+    FLOAT cosX = cos (xRot);
+    FLOAT sinY = sin (yRot);
+    FLOAT cosY = cos (yRot);
+    FLOAT sinZ = sin (zRot);
+    FLOAT cosZ = cos (zRot);
+
+    for (n = 0; n < st->blotCount; n++)
+    {
+       FLOAT x1 = st->blots[n].x;
+       FLOAT y1 = st->blots[n].y;
+       FLOAT z1 = st->blots[n].z;
+       FLOAT x2, y2, z2;
+
+       /* rotate on z axis */
+       x2 = x1 * cosZ - y1 * sinZ;
+       y2 = x1 * sinZ + y1 * cosZ;
+       z2 = z1;
+
+       /* rotate on x axis */
+       y1 = y2 * cosX - z2 * sinX;
+       z1 = y2 * sinX + z2 * cosX;
+       x1 = x2;
+
+       /* rotate on y axis */
+       z2 = z1 * cosY - x1 * sinY;
+       x2 = z1 * sinY + x1 * cosY;
+       y2 = y1;
+
+       st->blots[n].x = x2;
+       st->blots[n].y = y2;
+       st->blots[n].z = z2;
+    }
+}
+
+
+
+/*
+ * blot configurations
+ */
+
 /* set up the initial array of blots to be a at the edge of a sphere */
-static void setupBlotsSphere (void)
+static void setupBlotsSphere (struct state *st)
 {
     int n;
 
-    blotCount = requestedBlotCount;
-    blots = calloc (sizeof (Blot), blotCount);
+    st->blotCount = st->requestedBlotCount;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
 
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
        /* pick a spot, but reject if its radius is < 0.2 or > 1 to
         * avoid scaling problems */
@@ -276,19 +284,18 @@ static void setupBlotsSphere (void)
        y /= radius;
        z /= radius;
 
-       initBlot (&blots[n], x, y, z);
+       initBlot (&st->blots[n], x, y, z);
     }
-
 }
 
 /* set up the initial array of blots to be a simple cube */
-static void setupBlotsCube (void)
+static void setupBlotsCube (struct state *st)
 {
     int i, j, k, n;
 
     /* derive blotsPerEdge from blotCount, but then do the reverse
      * since roundoff may have changed blotCount */
-    int blotsPerEdge = ((requestedBlotCount - 8) / 12) + 2;
+    int blotsPerEdge = ((st->requestedBlotCount - 8) / 12) + 2;
     FLOAT distBetween;
 
     if (blotsPerEdge < 2)
@@ -298,8 +305,8 @@ static void setupBlotsCube (void)
 
     distBetween = 2.0 / (blotsPerEdge - 1.0);
 
-    blotCount = 8 + (blotsPerEdge - 2) * 12;
-    blots = calloc (sizeof (Blot), blotCount);
+    st->blotCount = 8 + (blotsPerEdge - 2) * 12;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
     n = 0;
 
     /* define the corners */
@@ -309,7 +316,7 @@ static void setupBlotsCube (void)
        {
            for (k = -1; k < 2; k += 2)
            {
-               initBlot (&blots[n], i, j, k);
+               initBlot (&st->blots[n], i, j, k);
                n++;
            } 
        }
@@ -319,35 +326,42 @@ static void setupBlotsCube (void)
     for (i = 1; i < (blotsPerEdge - 1); i++)
     {
        FLOAT varEdge = distBetween * i - 1;
-       initBlot (&blots[n++], varEdge, -1, -1);
-       initBlot (&blots[n++], varEdge,  1, -1);
-       initBlot (&blots[n++], varEdge, -1,  1);
-       initBlot (&blots[n++], varEdge,  1,  1);
-       initBlot (&blots[n++], -1, varEdge, -1);
-       initBlot (&blots[n++],  1, varEdge, -1);
-       initBlot (&blots[n++], -1, varEdge,  1);
-       initBlot (&blots[n++],  1, varEdge,  1);
-       initBlot (&blots[n++], -1, -1, varEdge);
-       initBlot (&blots[n++],  1, -1, varEdge);
-       initBlot (&blots[n++], -1,  1, varEdge);
-       initBlot (&blots[n++],  1,  1, varEdge);
-    }
-
-    scaleBlotsToRadius1 ();
-    randomlyReorderBlots ();
-}
+       initBlot (&st->blots[n++], varEdge, -1, -1);
+       initBlot (&st->blots[n++], varEdge,  1, -1);
+       initBlot (&st->blots[n++], varEdge, -1,  1);
+       initBlot (&st->blots[n++], varEdge,  1,  1);
+       initBlot (&st->blots[n++], -1, varEdge, -1);
+       initBlot (&st->blots[n++],  1, varEdge, -1);
+       initBlot (&st->blots[n++], -1, varEdge,  1);
+       initBlot (&st->blots[n++],  1, varEdge,  1);
+       initBlot (&st->blots[n++], -1, -1, varEdge);
+       initBlot (&st->blots[n++],  1, -1, varEdge);
+       initBlot (&st->blots[n++], -1,  1, varEdge);
+       initBlot (&st->blots[n++],  1,  1, varEdge);
+    }
 
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
+    randomlyRotateBlots (st);
+}
 
 /* set up the initial array of blots to be a cylinder */
-static void setupBlotsCylinder (void)
+static void setupBlotsCylinder (struct state *st)
 {
     int i, j, n;
-
-    /* derive blotsPerEdge from blotCount, but then do the reverse
-     * since roundoff may have changed blotCount */
-    int blotsPerEdge = requestedBlotCount / 32;
     FLOAT distBetween;
 
+    /* derive blotsPerEdge and blotsPerRing from blotCount, but then do the
+     * reverse since roundoff may have changed blotCount */
+    FLOAT reqRoot = sqrt ((FLOAT) st->requestedBlotCount);
+    int blotsPerRing = ceil (RAND_FLOAT_PM1 * reqRoot) / 2 + reqRoot;
+    int blotsPerEdge = st->requestedBlotCount / blotsPerRing;
+
+    if (blotsPerRing < 2)
+    {
+       blotsPerRing = 2;
+    }
+
     if (blotsPerEdge < 2)
     {
        blotsPerEdge = 2;
@@ -355,36 +369,39 @@ static void setupBlotsCylinder (void)
 
     distBetween = 2.0 / (blotsPerEdge - 1);
 
-    blotCount = blotsPerEdge * 32;
-    blots = calloc (sizeof (Blot), blotCount);
+    st->blotCount = blotsPerEdge * blotsPerRing;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
     n = 0;
 
     /* define the edges */
-    for (i = 0; i < 32; i++)
+    for (i = 0; i < blotsPerRing; i++)
     {
-       FLOAT x = sin (2 * M_PI / 32 * i);
-       FLOAT y = cos (2 * M_PI / 32 * i);
+       FLOAT x = sin (2 * M_PI / blotsPerRing * i);
+       FLOAT y = cos (2 * M_PI / blotsPerRing * i);
        for (j = 0; j < blotsPerEdge; j++)
        {
-           initBlot (&blots[n], x, y, j * distBetween - 1);
+           initBlot (&st->blots[n], x, y, j * distBetween - 1);
            n++;
        }
     }
 
-    scaleBlotsToRadius1 ();
-    randomlyReorderBlots ();
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
+    randomlyRotateBlots (st);
 }
 
-
-
 /* set up the initial array of blots to be a squiggle */
-static void setupBlotsSquiggle (void)
+static void setupBlotsSquiggle (struct state *st)
 {
     FLOAT x, y, z, xv, yv, zv, len;
+    int minCoor, maxCoor;
     int n;
 
-    blotCount = requestedBlotCount;
-    blots = calloc (sizeof (Blot), blotCount);
+    st->blotCount = st->requestedBlotCount;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
+
+    maxCoor = (int) (RAND_FLOAT_01 * 5) + 1;
+    minCoor = -maxCoor;
 
     x = RAND_FLOAT_PM1;
     y = RAND_FLOAT_PM1;
@@ -398,10 +415,10 @@ static void setupBlotsSquiggle (void)
     yv /= len;
     zv /= len;
     
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
        FLOAT newx, newy, newz;
-       initBlot (&blots[n], x, y, z);
+       initBlot (&st->blots[n], x, y, z);
 
        for (;;)
        {
@@ -417,9 +434,9 @@ static void setupBlotsSquiggle (void)
            newy = y + yv * 0.1;
            newz = z + zv * 0.1;
 
-           if (   (newx >= -1) && (newx <= 1)
-               && (newy >= -1) && (newy <= 1)
-               && (newz >= -1) && (newz <= 1))
+           if (   (newx >= minCoor) && (newx <= maxCoor)
+               && (newy >= minCoor) && (newy <= maxCoor)
+               && (newz >= minCoor) && (newz <= maxCoor))
            {
                break;
            }
@@ -430,66 +447,322 @@ static void setupBlotsSquiggle (void)
        z = newz;
     }
 
-    scaleBlotsToRadius1 ();
-    randomlyReorderBlots ();
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
 }
 
+/* set up the initial array of blots to be near the corners of a
+ * cube, distributed slightly */
+static void setupBlotsCubeCorners (struct state *st)
+{
+    int n;
 
+    st->blotCount = st->requestedBlotCount;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
 
-/* free the blots, in preparation for a new shape */
-static void freeBlots (void)
+    for (n = 0; n < st->blotCount; n++)
+    {
+       FLOAT x = rint (RAND_FLOAT_01) * 2 - 1;
+       FLOAT y = rint (RAND_FLOAT_01) * 2 - 1;
+       FLOAT z = rint (RAND_FLOAT_01) * 2 - 1;
+
+       x += RAND_FLOAT_PM1 * 0.3;
+       y += RAND_FLOAT_PM1 * 0.3;
+       z += RAND_FLOAT_PM1 * 0.3;
+
+       initBlot (&st->blots[n], x, y, z);
+    }
+
+    scaleBlotsToRadius1 (st);
+    randomlyRotateBlots (st);
+}
+
+/* set up the initial array of blots to be randomly distributed
+ * on the surface of a tetrahedron */
+static void setupBlotsTetrahedron (struct state *st)
+{
+    /* table of corners of the tetrahedron */
+    static const FLOAT cor[4][3] = { {  0.0,   1.0,  0.0 },
+                                     { -0.75, -0.5, -0.433013 },
+                                     {  0.0,  -0.5,  0.866025 },
+                                     {  0.75, -0.5, -0.433013 } };
+
+    int n, c;
+
+    /* derive blotsPerSurface from blotCount, but then do the reverse
+     * since roundoff may have changed blotCount */
+    int blotsPerSurface = st->requestedBlotCount / 4;
+
+    st->blotCount = blotsPerSurface * 4;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
+
+    for (n = 0; n < st->blotCount; n += 4)
+    {
+       /* pick a random point on a unit right triangle */
+       FLOAT rawx = RAND_FLOAT_01;
+       FLOAT rawy = RAND_FLOAT_01;
+
+       if ((rawx + rawy) > 1)
+       {
+           /* swap coords into place */
+           FLOAT t = 1.0 - rawx;
+           rawx = 1.0 - rawy;
+           rawy = t;
+       }
+
+       /* translate the point to be on each of the surfaces */
+       for (c = 0; c < 4; c++)
+       {
+           FLOAT x, y, z;
+           
+           int c1 = (c + 1) % 4;
+           int c2 = (c + 2) % 4;
+           
+           x = (cor[c1][0] - cor[c][0]) * rawx + 
+               (cor[c2][0] - cor[c][0]) * rawy + 
+               cor[c][0];
+
+           y = (cor[c1][1] - cor[c][1]) * rawx + 
+               (cor[c2][1] - cor[c][1]) * rawy + 
+               cor[c][1];
+
+           z = (cor[c1][2] - cor[c][2]) * rawx + 
+               (cor[c2][2] - cor[c][2]) * rawy + 
+               cor[c][2];
+
+           initBlot (&st->blots[n + c], x, y, z);
+       }
+    }
+
+    randomlyRotateBlots (st);
+}
+
+/* set up the initial array of blots to be an almost-evenly-distributed
+ * square sheet */
+static void setupBlotsSheet (struct state *st)
+{
+    int x, y;
+
+    int blotsPerDimension = floor (sqrt (st->requestedBlotCount));
+    FLOAT spaceBetween;
+
+    if (blotsPerDimension < 2)
+    {
+       blotsPerDimension = 2;
+    }
+
+    spaceBetween = 2.0 / (blotsPerDimension - 1);
+
+    st->blotCount = blotsPerDimension * blotsPerDimension;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
+
+    for (x = 0; x < blotsPerDimension; x++)
+    {
+       for (y = 0; y < blotsPerDimension; y++)
+       {
+           FLOAT x1 = x * spaceBetween - 1.0;
+           FLOAT y1 = y * spaceBetween - 1.0;
+           FLOAT z1 = 0.0;
+
+           x1 += RAND_FLOAT_PM1 * spaceBetween / 3;
+           y1 += RAND_FLOAT_PM1 * spaceBetween / 3;
+           z1 += RAND_FLOAT_PM1 * spaceBetween / 2;
+
+           initBlot (&st->blots[x + y * blotsPerDimension], x1, y1, z1);
+       }
+    }
+
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
+    randomlyRotateBlots (st);
+}
+
+/* set up the initial array of blots to be a swirlycone */
+static void setupBlotsSwirlyCone (struct state *st)
+{
+    FLOAT radSpace = 1.0 / (st->requestedBlotCount - 1);
+    FLOAT zSpace = radSpace * 2;
+    FLOAT rotAmt = RAND_FLOAT_PM1 * M_PI / 10;
+
+    int n;
+    FLOAT rot = 0.0;
+
+    st->blotCount = st->requestedBlotCount;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
+
+    for (n = 0; n < st->blotCount; n++)
+    {
+       FLOAT radius = n * radSpace;
+       FLOAT x = cos (rot) * radius;
+       FLOAT y = sin (rot) * radius;
+       FLOAT z = n * zSpace - 1.0;
+
+       rot += rotAmt;
+       initBlot (&st->blots[n], x, y, z);
+    }
+
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
+    randomlyRotateBlots (st);
+}
+
+/* forward declaration for recursive use immediately below */
+static void setupBlots (struct state *st);
+
+/* set up the blots to be two of the other choices, placed next to
+ * each other */
+static void setupBlotsDuo (struct state *st)
 {
-    if (blots != NULL)
+    int origRequest = st->requestedBlotCount;
+    FLOAT tx, ty, tz, radius;
+    Blot *blots1, *blots2;
+    int count1, count2;
+    int n;
+
+    if (st->requestedBlotCount < 15)
     {
-       free (blots);
-       blots = NULL;
+       /* special case bottom-out */
+       setupBlotsSphere (st);
+       return;
     }
 
-    if (segsToErase != NULL)
+    tx = RAND_FLOAT_PM1;
+    ty = RAND_FLOAT_PM1;
+    tz = RAND_FLOAT_PM1;
+    radius = sqrt (tx * tx + ty * ty + tz * tz);
+    tx /= radius;
+    ty /= radius;
+    tz /= radius;
+
+    /* recursive call to setup set 1 */
+    st->requestedBlotCount = origRequest / 2;
+    setupBlots (st);
+
+    if (st->blotCount >= origRequest)
     {
-       free (segsToErase);
-       segsToErase = NULL;
+       /* return immediately if this satisfies the original count request */
+       st->requestedBlotCount = origRequest;
+       return;
     }
 
-    if (segsToDraw != NULL)
+    blots1 = st->blots;
+    count1 = st->blotCount;
+    st->blots = NULL;
+    st->blotCount = 0;
+    
+    /* translate to new position */
+    for (n = 0; n < count1; n++)
     {
-       free (segsToDraw);
-       segsToDraw = NULL;
+       blots1[n].x += tx;
+       blots1[n].y += ty;
+       blots1[n].z += tz;
     }
+
+    /* recursive call to setup set 2 */
+    st->requestedBlotCount = origRequest - count1;
+    setupBlots (st);
+    blots2 = st->blots;
+    count2 = st->blotCount;
+
+    /* translate to new position */
+    for (n = 0; n < count2; n++)
+    {
+       blots2[n].x -= tx;
+       blots2[n].y -= ty;
+       blots2[n].z -= tz;
+    }
+
+    /* combine the two arrays */
+    st->blotCount = count1 + count2;
+    st->blots = calloc (sizeof (Blot), st->blotCount);
+    memcpy (&st->blots[0],      blots1, sizeof (Blot) * count1);
+    memcpy (&st->blots[count1], blots2, sizeof (Blot) * count2);
+    free (blots1);
+    free (blots2);
+
+    scaleBlotsToRadius1 (st);
+    randomlyReorderBlots (st);
+
+    /* restore the original requested count, for future iterations */
+    st->requestedBlotCount = origRequest;
 }
 
 
 
+/*
+ * main blot setup
+ */
+
+/* free the blots, in preparation for a new shape */
+static void freeBlots (struct state *st)
+{
+    if (st->blots != NULL)
+    {
+       free (st->blots);
+       st->blots = NULL;
+    }
+
+    if (st->segsToErase != NULL)
+    {
+       free (st->segsToErase);
+       st->segsToErase = NULL;
+    }
+
+    if (st->segsToDraw != NULL)
+    {
+       free (st->segsToDraw);
+       st->segsToDraw = NULL;
+    }
+}
+
 /* set up the initial arrays of blots */
-static void setupBlots (void)
+static void setupBlots (struct state *st)
 {
-    int which = RAND_FLOAT_01 * 4;
+    int which = RAND_FLOAT_01 * 11;
 
-    freeBlots ();
+    freeBlots (st);
 
     switch (which)
     {
        case 0:
-           setupBlotsCube ();
+           setupBlotsCube (st);
            break;
        case 1:
-           setupBlotsSphere ();
+           setupBlotsSphere (st);
            break;
        case 2:
-           setupBlotsCylinder ();
+           setupBlotsCylinder (st);
            break;
        case 3:
-           setupBlotsSquiggle ();
+           setupBlotsSquiggle (st);
+           break;
+       case 4:
+           setupBlotsCubeCorners (st);
+           break;
+       case 5:
+           setupBlotsTetrahedron (st);
+           break;
+       case 6:
+           setupBlotsSheet (st);
+           break;
+       case 7:
+           setupBlotsSwirlyCone (st);
+           break;
+       case 8:
+       case 9:
+       case 10:
+           setupBlotsDuo (st);
            break;
     }
+}
 
+/* set up the segments arrays */
+static void setupSegs (struct state *st)
+{
     /* there are blotShapeCount - 1 line segments per blot */
-    segCount = blotCount * (blotShapeCount - 1);
-    segsToErase = calloc (sizeof (LineSegment), segCount);
-    segsToDraw = calloc (sizeof (LineSegment), segCount);
-
-    /* erase the world */
-    XFillRectangle (display, window, gcs[0], 0, 0, windowWidth, windowHeight);
+    st->segCount = st->blotCount * (blotShapeCount - 1);
+    st->segsToErase = calloc (sizeof (LineSegment), st->segCount);
+    st->segsToDraw = calloc (sizeof (LineSegment), st->segCount);
 }
 
 
@@ -499,11 +772,11 @@ static void setupBlots (void)
  */
 
 /* set up the colormap */
-static void setupColormap (XWindowAttributes *xgwa)
+static void setupColormap (struct state *st, XWindowAttributes *xgwa)
 {
     int n;
     XGCValues gcv;
-    XColor *colors = (XColor *) calloc (sizeof (XColor), colorCount + 1);
+    XColor *colors = (XColor *) calloc (sizeof (XColor), st->colorCount + 1);
 
     unsigned short r, g, b;
     int h1, h2;
@@ -523,25 +796,26 @@ static void setupColormap (XWindowAttributes *xgwa)
     s2 = 0.7;
     v2 = 0.7;
     
-    colors[0].pixel = get_pixel_resource ("background", "Background",
-                                         display, xgwa->colormap);
+    colors[0].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
+                                          "background", "Background");
     
-    make_color_ramp (display, xgwa->colormap, h1, s1, v1, h2, s2, v2,
-                    colors + 1, &colorCount, False, True, False);
+    make_color_ramp (xgwa->screen, xgwa->visual, xgwa->colormap,
+                     h1, s1, v1, h2, s2, v2,
+                    colors + 1, &st->colorCount, False, True, False);
 
-    if (colorCount < 1)
+    if (st->colorCount < 1)
     {
         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
        exit (-1);
     }
     
-    gcs = (GC *) calloc (sizeof (GC), colorCount + 1);
+    st->gcs = (GC *) calloc (sizeof (GC), st->colorCount + 1);
 
-    for (n = 0; n <= colorCount; n++) 
+    for (n = 0; n <= st->colorCount; n++) 
     {
        gcv.foreground = colors[n].pixel;
-       gcv.line_width = lineWidth;
-       gcs[n] = XCreateGC (display, window, GCForeground | GCLineWidth, &gcv);
+       gcv.line_width = st->lineWidth;
+       st->gcs[n] = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth, &gcv);
     }
 
     free (colors);
@@ -554,32 +828,43 @@ static void setupColormap (XWindowAttributes *xgwa)
  */
 
 /* set up the system */
-static void setup (void)
+static void setup (struct state *st)
 {
     XWindowAttributes xgwa;
 
-    XGetWindowAttributes (display, window, &xgwa);
+    XGetWindowAttributes (st->dpy, st->window, &xgwa);
 
-    windowWidth = xgwa.width;
-    windowHeight = xgwa.height;
-    centerX = windowWidth / 2;
-    centerY = windowHeight / 2;
-    baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
+    st->windowWidth = xgwa.width;
+    st->windowHeight = xgwa.height;
+    st->centerX = st->windowWidth / 2;
+    st->centerY = st->windowHeight / 2;
+    st->baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
+
+    if (st->doubleBuffer)
+    {
+       st->drawable = XCreatePixmap (st->dpy, st->window, xgwa.width, xgwa.height,
+                                 xgwa.depth);
+    }
+    else
+    {
+       st->drawable = st->window;
+    }
 
-    setupColormap (&xgwa);
-    setupBlots ();
+    setupColormap (st, &xgwa);
+    setupBlots (st);
+    setupSegs (st);
 
     /* set up the initial rotation, scale, and light values as random, but
      * with the targets equal to where it is */
-    xRot = xRotTarget = RAND_FLOAT_01 * M_PI;
-    yRot = yRotTarget = RAND_FLOAT_01 * M_PI;
-    zRot = zRotTarget = RAND_FLOAT_01 * M_PI;
-    curScale = scaleTarget = RAND_FLOAT_01 * (maxScale - minScale) + minScale;
-    lightX = lightXTarget = RAND_FLOAT_PM1;
-    lightY = lightYTarget = RAND_FLOAT_PM1;
-    lightZ = lightZTarget = RAND_FLOAT_PM1;
-
-    itersTillNext = RAND_FLOAT_01 * 1234;
+    st->xRot = st->xRotTarget = RAND_FLOAT_01 * M_PI;
+    st->yRot = st->yRotTarget = RAND_FLOAT_01 * M_PI;
+    st->zRot = st->zRotTarget = RAND_FLOAT_01 * M_PI;
+    st->curScale = st->scaleTarget = RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
+    st->lightX = st->lightXTarget = RAND_FLOAT_PM1;
+    st->lightY = st->lightYTarget = RAND_FLOAT_PM1;
+    st->lightZ = st->lightZTarget = RAND_FLOAT_PM1;
+
+    st->itersTillNext = RAND_FLOAT_01 * st->maxIters;
 }
 
 
@@ -589,22 +874,22 @@ static void setup (void)
  */
 
 /* "render" the blots into segsToDraw, with the current rotation factors */
-static void renderSegs (void)
+static void renderSegs (struct state *st)
 {
     int n;
     int m = 0;
 
     /* rotation factors */
-    FLOAT sinX = sin (xRot);
-    FLOAT cosX = cos (xRot);
-    FLOAT sinY = sin (yRot);
-    FLOAT cosY = cos (yRot);
-    FLOAT sinZ = sin (zRot);
-    FLOAT cosZ = cos (zRot);
-
-    for (n = 0; n < blotCount; n++)
+    FLOAT sinX = sin (st->xRot);
+    FLOAT cosX = cos (st->xRot);
+    FLOAT sinY = sin (st->yRot);
+    FLOAT cosY = cos (st->yRot);
+    FLOAT sinZ = sin (st->zRot);
+    FLOAT cosZ = cos (st->zRot);
+
+    for (n = 0; n < st->blotCount; n++)
     {
-       Blot *b = &blots[n];
+       Blot *b = &st->blots[n];
        int i, j;
        int baseX, baseY;
        FLOAT radius;
@@ -612,9 +897,9 @@ static void renderSegs (void)
        int y[3][3];
        int color;
 
-       FLOAT x1 = blots[n].x;
-       FLOAT y1 = blots[n].y;
-       FLOAT z1 = blots[n].z;
+       FLOAT x1 = st->blots[n].x;
+       FLOAT y1 = st->blots[n].y;
+       FLOAT z1 = st->blots[n].z;
        FLOAT x2, y2, z2;
 
        /* rotate on z axis */
@@ -634,73 +919,74 @@ static void renderSegs (void)
 
        /* the color to draw is based on the distance from the light of
         * the post-rotation blot */
-       x1 = x2 - lightX;
-       y1 = y2 - lightY;
-       z1 = z2 - lightZ;
-       color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * colorCount;
-       if (color > colorCount)
+       x1 = x2 - st->lightX;
+       y1 = y2 - st->lightY;
+       z1 = z2 - st->lightZ;
+       color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * st->colorCount;
+       if (color > st->colorCount)
        {
-           color = colorCount;
+           color = st->colorCount;
        }
 
        /* set up the base screen coordinates for drawing */
-       baseX = x2 / 2 * baseScale * curScale + centerX + centerXOff;
-       baseY = y2 / 2 * baseScale * curScale + centerY + centerYOff;
+       baseX = x2 / 2 * st->baseScale * st->curScale + st->centerX + st->centerXOff;
+       baseY = y2 / 2 * st->baseScale * st->curScale + st->centerY + st->centerYOff;
        
-       radius = (z2 + 1) / 2 * (maxRadius - minRadius) + minRadius;
+       radius = (z2 + 1) / 2 * (st->maxRadius - st->minRadius) + st->minRadius;
 
        for (i = 0; i < 3; i++)
        {
            for (j = 0; j < 3; j++)
            {
                x[i][j] = baseX + 
-                   ((i - 1) + (b->xoff[i][j] * maxNerveRadius)) * radius;
+                   ((i - 1) + (b->xoff[i][j] * st->maxNerveRadius)) * radius;
                y[i][j] = baseY + 
-                   ((j - 1) + (b->yoff[i][j] * maxNerveRadius)) * radius;
+                   ((j - 1) + (b->yoff[i][j] * st->maxNerveRadius)) * radius;
            }
        }
 
        for (i = 1; i < blotShapeCount; i++)
        {
-           segsToDraw[m].gc = gcs[color];
-           segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
-           segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
-           segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
-           segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
+           st->segsToDraw[m].gc = st->gcs[color];
+           st->segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
+           st->segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
+           st->segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
+           st->segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
            m++;
        }
     }
 }
 
 /* update blots, adjusting the offsets and rotation factors. */
-static void updateWithFeeling (void)
+static void updateWithFeeling (struct state *st)
 {
     int n, i, j;
 
     /* pick a new model if the time is right */
-    itersTillNext--;
-    if (itersTillNext < 0)
+    st->itersTillNext--;
+    if (st->itersTillNext < 0)
     {
-       itersTillNext = RAND_FLOAT_01 * 1234;
-       setupBlots ();
-       renderSegs ();
+       st->itersTillNext = RAND_FLOAT_01 * st->maxIters;
+       setupBlots (st);
+       setupSegs (st);
+       renderSegs (st);
     }
 
     /* update the rotation factors by moving them a bit toward the targets */
-    xRot = xRot + (xRotTarget - xRot) * iterAmt; 
-    yRot = yRot + (yRotTarget - yRot) * iterAmt;
-    zRot = zRot + (zRotTarget - zRot) * iterAmt;
+    st->xRot = st->xRot + (st->xRotTarget - st->xRot) * st->iterAmt; 
+    st->yRot = st->yRot + (st->yRotTarget - st->yRot) * st->iterAmt;
+    st->zRot = st->zRot + (st->zRotTarget - st->zRot) * st->iterAmt;
 
     /* similarly the scale factor */
-    curScale = curScale + (scaleTarget - curScale) * iterAmt;
+    st->curScale = st->curScale + (st->scaleTarget - st->curScale) * st->iterAmt;
 
     /* and similarly the light position */
-    lightX = lightX + (lightXTarget - lightX) * iterAmt; 
-    lightY = lightY + (lightYTarget - lightY) * iterAmt; 
-    lightZ = lightZ + (lightZTarget - lightZ) * iterAmt; 
+    st->lightX = st->lightX + (st->lightXTarget - st->lightX) * st->iterAmt; 
+    st->lightY = st->lightY + (st->lightYTarget - st->lightY) * st->iterAmt; 
+    st->lightZ = st->lightZ + (st->lightZTarget - st->lightZ) * st->iterAmt; 
 
     /* for each blot... */
-    for (n = 0; n < blotCount; n++)
+    for (n = 0; n < st->blotCount; n++)
     {
        /* add a bit of random jitter to xoff/yoff */
        for (i = 0; i < 3; i++)
@@ -709,105 +995,105 @@ static void updateWithFeeling (void)
            {
                FLOAT newOff;
 
-               newOff = blots[n].xoff[i][j] + RAND_FLOAT_PM1 * nervousness;
+               newOff = st->blots[n].xoff[i][j] + RAND_FLOAT_PM1 * st->nervousness;
                if (newOff < -1) newOff = -(newOff + 1) - 1;
                else if (newOff > 1) newOff = -(newOff - 1) + 1;
-               blots[n].xoff[i][j] = newOff;
+               st->blots[n].xoff[i][j] = newOff;
 
-               newOff = blots[n].yoff[i][j] + RAND_FLOAT_PM1 * nervousness;
+               newOff = st->blots[n].yoff[i][j] + RAND_FLOAT_PM1 * st->nervousness;
                if (newOff < -1) newOff = -(newOff + 1) - 1;
                else if (newOff > 1) newOff = -(newOff - 1) + 1;
-               blots[n].yoff[i][j] = newOff;
+               st->blots[n].yoff[i][j] = newOff;
            }
        }
     }
 
     /* depending on random chance, update one or more factors */
-    if (RAND_FLOAT_01 <= eventChance)
+    if (RAND_FLOAT_01 <= st->eventChance)
     {
        int which = RAND_FLOAT_01 * 14;
        switch (which)
        {
            case 0:
            {
-               xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 1:
            {
-               yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 2:
            {
-               zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 3:
            {
-               xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
-               yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 4:
            {
-               xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
-               zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 5:
            {
-               yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
-               zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 6:
            {
-               xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
-               yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
-               zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
+               st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
                break;
            }
            case 7:
            {
-               centerXOff = RAND_FLOAT_PM1 * maxRadius / 2;
+               st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
                break;
            }
            case 8:
            {
-               centerYOff = RAND_FLOAT_PM1 * maxRadius / 2;
+               st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
                break;
            }
            case 9:
            {
-               centerXOff = RAND_FLOAT_PM1 * maxRadius / 2;
-               centerYOff = RAND_FLOAT_PM1 * maxRadius / 2;
+               st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
+               st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
                break;
            }
            case 10:
            {
-               scaleTarget = 
-                   RAND_FLOAT_01 * (maxScale - minScale) + minScale;
+               st->scaleTarget = 
+                   RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
                break;
            }
            case 11:
            {
-               curScale = 
-                   RAND_FLOAT_01 * (maxScale - minScale) + minScale;
+               st->curScale = 
+                   RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
                break;
            }
            case 12:
            {
-               lightX = RAND_FLOAT_PM1;
-               lightY = RAND_FLOAT_PM1;
-               lightZ = RAND_FLOAT_PM1;
+               st->lightX = RAND_FLOAT_PM1;
+               st->lightY = RAND_FLOAT_PM1;
+               st->lightZ = RAND_FLOAT_PM1;
                break;
            }
            case 13:
            {
-               lightXTarget = RAND_FLOAT_PM1;
-               lightYTarget = RAND_FLOAT_PM1;
-               lightZTarget = RAND_FLOAT_PM1;
+               st->lightXTarget = RAND_FLOAT_PM1;
+               st->lightYTarget = RAND_FLOAT_PM1;
+               st->lightZTarget = RAND_FLOAT_PM1;
                break;
            }
        }
@@ -815,172 +1101,166 @@ static void updateWithFeeling (void)
 }
 
 /* erase segsToErase and draw segsToDraw */
-static void eraseAndDraw (void)
+static void eraseAndDraw (struct state *st)
 {
     int n;
 
-    for (n = 0; n < segCount; n++)
+    if (st->doubleBuffer)
+      XFillRectangle (st->dpy, st->drawable, st->gcs[0], 0, 0, 
+                      st->windowWidth, st->windowHeight);
+    else
+      XClearWindow (st->dpy, st->drawable);
+
+    for (n = 0; n < st->segCount; n++)
     {
-       LineSegment *seg = &segsToErase[n];
-       XDrawLine (display, window, gcs[0], 
+       LineSegment *seg = &st->segsToErase[n];
+#ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
+       XDrawLine (st->dpy, st->drawable, st->gcs[0], 
                   seg->x1, seg->y1, seg->x2, seg->y2);
-       seg = &segsToDraw[n];
-       XDrawLine (display, window, seg->gc,
+#endif
+       seg = &st->segsToDraw[n];
+       XDrawLine (st->dpy, st->drawable, seg->gc,
                   seg->x1, seg->y1, seg->x2, seg->y2);
     }
+
+    if (st->doubleBuffer)
+    {
+       XCopyArea (st->dpy, st->drawable, st->window, st->gcs[0], 0, 0, 
+                  st->windowWidth, st->windowHeight, 0, 0);
+    }
 }
 
 /* do one iteration */
-static void oneIteration (void)
+static unsigned long
+nerverot_draw (Display *dpy, Window win, void *closure)
 {
+  struct state *st = (struct state *) closure;
     /* switch segsToErase and segsToDraw */
-    LineSegment *temp = segsToDraw;
-    segsToDraw = segsToErase;
-    segsToErase = temp;
+    LineSegment *temp = st->segsToDraw;
+    st->segsToDraw = st->segsToErase;
+    st->segsToErase = temp;
 
     /* update the model */
-    updateWithFeeling ();
+    updateWithFeeling (st);
 
     /* render new segments */
-    renderSegs ();
+    renderSegs (st);
 
     /* erase old segments and draw new ones */
-    eraseAndDraw ();
-}
-
-char *progclass = "NerveRot";
+    eraseAndDraw (st);
 
-char *defaults [] = {
-    ".background:      black",
-    ".foreground:      white",
-    "*count:           250",
-    "*colors:          4",
-    "*delay:           10000",
-    "*eventChance:      0.2",
-    "*iterAmt:          0.01",
-    "*lineWidth:        0",
-    "*minScale:         0.6",
-    "*maxScale:         1.75",
-    "*minRadius:        3",
-    "*maxRadius:        25",
-    "*maxNerveRadius:  0.7",
-    "*nervousness:     0.3",
-    0
-};
-
-XrmOptionDescRec options [] = {
-  { "-count",            ".count",          XrmoptionSepArg, 0 },
-  { "-colors",           ".colors",         XrmoptionSepArg, 0 },
-  { "-cube",             ".cube",           XrmoptionNoArg,  "true" },
-  { "-delay",            ".delay",          XrmoptionSepArg, 0 },
-  { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
-  { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
-  { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
-  { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
-  { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
-  { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
-  { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
-  { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
-  { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
-  { 0, 0, 0, 0 }
-};
+    return st->delay;
+}
 
 /* initialize the user-specifiable params */
-static void initParams (void)
+static void initParams (struct state *st)
 {
     int problems = 0;
 
-    delay = get_integer_resource ("delay", "Delay");
-    if (delay < 0)
+    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->maxIters = get_integer_resource (st->dpy, "maxIters", "Integer");
+    if (st->maxIters < 0)
+    {
+       fprintf (stderr, "error: maxIters must be at least 0\n");
+       problems = 1;
+    }
+    
+    st->doubleBuffer = get_boolean_resource (st->dpy, "doubleBuffer", "Boolean");
+
+# ifdef HAVE_COCOA     /* Don't second-guess Quartz's double-buffering */
+    st->doubleBuffer = False;
+# endif
 
-    requestedBlotCount = get_integer_resource ("count", "Count");
-    if (requestedBlotCount <= 0)
+    st->requestedBlotCount = get_integer_resource (st->dpy, "count", "Count");
+    if (st->requestedBlotCount <= 0)
     {
        fprintf (stderr, "error: count must be at least 0\n");
        problems = 1;
     }
 
-    colorCount = get_integer_resource ("colors", "Colors");
-    if (colorCount <= 0)
+    st->colorCount = get_integer_resource (st->dpy, "colors", "Colors");
+    if (st->colorCount <= 0)
     {
        fprintf (stderr, "error: colors must be at least 1\n");
        problems = 1;
     }
 
-    lineWidth = get_integer_resource ("lineWidth", "LineWidth");
-    if (lineWidth < 0)
+    st->lineWidth = get_integer_resource (st->dpy, "lineWidth", "LineWidth");
+    if (st->lineWidth < 0)
     {
        fprintf (stderr, "error: line width must be at least 0\n");
        problems = 1;
     }
 
-    nervousness = get_float_resource ("nervousness", "Float");
-    if ((nervousness < 0) || (nervousness > 1))
+    st->nervousness = get_float_resource (st->dpy, "nervousness", "Float");
+    if ((st->nervousness < 0) || (st->nervousness > 1))
     {
        fprintf (stderr, "error: nervousness must be in the range 0..1\n");
        problems = 1;
     }
 
-    maxNerveRadius = get_float_resource ("maxNerveRadius", "Float");
-    if ((maxNerveRadius < 0) || (maxNerveRadius > 1))
+    st->maxNerveRadius = get_float_resource (st->dpy, "maxNerveRadius", "Float");
+    if ((st->maxNerveRadius < 0) || (st->maxNerveRadius > 1))
     {
        fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
        problems = 1;
     }
 
-    eventChance = get_float_resource ("eventChance", "Float");
-    if ((eventChance < 0) || (eventChance > 1))
+    st->eventChance = get_float_resource (st->dpy, "eventChance", "Float");
+    if ((st->eventChance < 0) || (st->eventChance > 1))
     {
        fprintf (stderr, "error: eventChance must be in the range 0..1\n");
        problems = 1;
     }
 
-    iterAmt = get_float_resource ("iterAmt", "Float");
-    if ((iterAmt < 0) || (iterAmt > 1))
+    st->iterAmt = get_float_resource (st->dpy, "iterAmt", "Float");
+    if ((st->iterAmt < 0) || (st->iterAmt > 1))
     {
        fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
        problems = 1;
     }
 
-    minScale = get_float_resource ("minScale", "Float");
-    if ((minScale < 0) || (minScale > 10))
+    st->minScale = get_float_resource (st->dpy, "minScale", "Float");
+    if ((st->minScale < 0) || (st->minScale > 10))
     {
        fprintf (stderr, "error: minScale must be in the range 0..10\n");
        problems = 1;
     }
 
-    maxScale = get_float_resource ("maxScale", "Float");
-    if ((maxScale < 0) || (maxScale > 10))
+    st->maxScale = get_float_resource (st->dpy, "maxScale", "Float");
+    if ((st->maxScale < 0) || (st->maxScale > 10))
     {
        fprintf (stderr, "error: maxScale must be in the range 0..10\n");
        problems = 1;
     }
 
-    if (maxScale < minScale)
+    if (st->maxScale < st->minScale)
     {
        fprintf (stderr, "error: maxScale must be >= minScale\n");
        problems = 1;
     }  
 
-    minRadius = get_integer_resource ("minRadius", "Integer");
-    if ((minRadius < 1) || (minRadius > 100))
+    st->minRadius = get_integer_resource (st->dpy, "minRadius", "Integer");
+    if ((st->minRadius < 1) || (st->minRadius > 100))
     {
        fprintf (stderr, "error: minRadius must be in the range 1..100\n");
        problems = 1;
     }
 
-    maxRadius = get_integer_resource ("maxRadius", "Integer");
-    if ((maxRadius < 1) || (maxRadius > 100))
+    st->maxRadius = get_integer_resource (st->dpy, "maxRadius", "Integer");
+    if ((st->maxRadius < 1) || (st->maxRadius > 100))
     {
        fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
        problems = 1;
     }
 
-    if (maxRadius < minRadius)
+    if (st->maxRadius < st->minRadius)
     {
        fprintf (stderr, "error: maxRadius must be >= minRadius\n");
        problems = 1;
@@ -992,23 +1272,83 @@ static void initParams (void)
     }
 }
 
-/* main function */
-void screenhack (Display *dpy, Window win)
+static void *
+nerverot_init (Display *dpy, Window window)
 {
-    display = dpy;
-    window = win;
+  struct state *st = (struct state *) calloc (1, sizeof(*st));
+  st->dpy = dpy;
+  st->window = window;
 
-    initParams ();
-    setup ();
+    initParams (st);
+    setup (st);
 
     /* make a valid set to erase at first */
-    renderSegs ();
-    
-    for (;;) 
-    {
-       oneIteration ();
-        XSync (dpy, False);
-        screenhack_handle_events (dpy);
-       usleep (delay);
-    }
+    renderSegs (st);
+    return st;
+}
+
+static void
+nerverot_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
+{
+}
+
+static Bool
+nerverot_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+  return False;
+}
+
+static void
+nerverot_free (Display *dpy, Window window, void *closure)
+{
+  struct state *st = (struct state *) closure;
+  freeBlots (st);
+  free (st);
 }
+
+
+static const char *nerverot_defaults [] = {
+    ".background:      black",
+    ".foreground:      white",
+    "*count:           250",
+    "*colors:          4",
+    "*delay:           10000",
+    "*maxIters:                1200",
+    "*doubleBuffer:    false",
+    "*eventChance:      0.2",
+    "*iterAmt:          0.01",
+    "*lineWidth:        0",
+    "*minScale:         0.6",
+    "*maxScale:         1.75",
+    "*minRadius:        3",
+    "*maxRadius:        25",
+    "*maxNerveRadius:  0.7",
+    "*nervousness:     0.3",
+#ifdef USE_IPHONE
+    "*ignoreRotation:   True",
+#endif
+    0
+};
+
+static XrmOptionDescRec nerverot_options [] = {
+  { "-count",            ".count",          XrmoptionSepArg, 0 },
+  { "-colors",           ".colors",         XrmoptionSepArg, 0 },
+  { "-delay",            ".delay",          XrmoptionSepArg, 0 },
+  { "-max-iters",        ".maxIters",       XrmoptionSepArg, 0 },
+  { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
+  { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
+  { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
+  { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
+  { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
+  { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
+  { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
+  { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
+  { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
+  { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
+  { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
+  { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("NerveRot", nerverot)