/* nerverot, nervous rotation of random thingies, v1.4
* by Dan Bornstein, danfuzz@milk.com
* 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 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.
*
* The goal of this screensaver is to be interesting and compelling to
* watch, yet induce a state of nervous edginess in the viewer.
*
* See the included man page for more details.
*/
#include <math.h>
#include "screenhack.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))
/* structure of the model */
/* each point-like thingy to draw is represented as a blot */
typedef struct blot_s
{
FLOAT x; /* 3d x position (-1..1) */
FLOAT y; /* 3d y position (-1..1) */
FLOAT z; /* 3d z position (-1..1) */
FLOAT xoff[3][3]; /* display x offset per drawn point (-1..1) */
FLOAT yoff[3][3]; /* display x offset per drawn point (-1..1) */
} Blot;
/* each drawn line is represented as a LineSegment */
typedef struct linesegment_s
{
GC gc;
int x1;
int y1;
int x2;
int y2;
} LineSegment;
/* 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 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);
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 */
};
/*
* generic blot setup and manipulation
*/
/* initialize a blot with the given coordinates and random display offsets */
static void initBlot (Blot *b, FLOAT x, FLOAT y, FLOAT z)
{
int i, j;
b->x = x;
b->y = y;
b->z = z;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
b->xoff[i][j] = RAND_FLOAT_PM1;
b->yoff[i][j] = RAND_FLOAT_PM1;
}
}
}
/* scale the blots to have a max distance of 1 from the center */
static void scaleBlotsToRadius1 (struct state *st)
{
FLOAT max = 0.0;
int n;
for (n = 0; n < st->blotCount; n++)
{
FLOAT distSquare =
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;
}
}
if (max == 0.0)
{
return;
}
max = sqrt (max);
for (n = 0; n < st->blotCount; n++)
{
st->blots[n].x /= max;
st->blots[n].y /= max;
st->blots[n].z /= max;
}
}
/* randomly reorder the blots */
static void randomlyReorderBlots (struct state *st)
{
int n;
for (n = 0; n < st->blotCount; n++)
{
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 (struct state *st)
{
int n;
st->blotCount = st->requestedBlotCount;
st->blots = calloc (sizeof (Blot), st->blotCount);
for (n = 0; n < st->blotCount; n++)
{
/* pick a spot, but reject if its radius is < 0.2 or > 1 to
* avoid scaling problems */
FLOAT x, y, z, radius;
for (;;)
{
x = RAND_FLOAT_PM1;
y = RAND_FLOAT_PM1;
z = RAND_FLOAT_PM1;
radius = sqrt (x * x + y * y + z * z);
if ((radius >= 0.2) && (radius <= 1.0))
{
break;
}
}
x /= radius;
y /= radius;
z /= radius;
initBlot (&st->blots[n], x, y, z);
}
}
/* set up the initial array of blots to be a simple cube */
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 = ((st->requestedBlotCount - 8) / 12) + 2;
FLOAT distBetween;
if (blotsPerEdge < 2)
{
blotsPerEdge = 2;
}
distBetween = 2.0 / (blotsPerEdge - 1.0);
st->blotCount = 8 + (blotsPerEdge - 2) * 12;
st->blots = calloc (sizeof (Blot), st->blotCount);
n = 0;
/* define the corners */
for (i = -1; i < 2; i += 2)
{
for (j = -1; j < 2; j += 2)
{
for (k = -1; k < 2; k += 2)
{
initBlot (&st->blots[n], i, j, k);
n++;
}
}
}
/* define the edges */
for (i = 1; i < (blotsPerEdge - 1); i++)
{
FLOAT varEdge = distBetween * i - 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++], 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 (struct state *st)
{
int i, j, n;
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;
}
distBetween = 2.0 / (blotsPerEdge - 1);
st->blotCount = blotsPerEdge * blotsPerRing;
st->blots = calloc (sizeof (Blot), st->blotCount);
n = 0;
/* define the edges */
for (i = 0; i < blotsPerRing; i++)
{
FLOAT x = sin (2 * M_PI / blotsPerRing * i);
FLOAT y = cos (2 * M_PI / blotsPerRing * i);
for (j = 0; j < blotsPerEdge; j++)
{
initBlot (&st->blots[n], x, y, j * distBetween - 1);
n++;
}
}
scaleBlotsToRadius1 (st);
randomlyReorderBlots (st);
randomlyRotateBlots (st);
}
/* set up the initial array of blots to be a squiggle */
static void setupBlotsSquiggle (struct state *st)
{
FLOAT x, y, z, xv, yv, zv, len;
int minCoor, maxCoor;
int n;
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;
z = RAND_FLOAT_PM1;
xv = RAND_FLOAT_PM1;
yv = RAND_FLOAT_PM1;
zv = RAND_FLOAT_PM1;
len = sqrt (xv * xv + yv * yv + zv * zv);
xv /= len;
yv /= len;
zv /= len;
for (n = 0; n < st->blotCount; n++)
{
FLOAT newx, newy, newz;
initBlot (&st->blots[n], x, y, z);
for (;;)
{
xv += RAND_FLOAT_PM1 * 0.1;
yv += RAND_FLOAT_PM1 * 0.1;
zv += RAND_FLOAT_PM1 * 0.1;
len = sqrt (xv * xv + yv * yv + zv * zv);
xv /= len;
yv /= len;
zv /= len;
newx = x + xv * 0.1;
newy = y + yv * 0.1;
newz = z + zv * 0.1;
if ( (newx >= minCoor) && (newx <= maxCoor)
&& (newy >= minCoor) && (newy <= maxCoor)
&& (newz >= minCoor) && (newz <= maxCoor))
{
break;
}
}
x = newx;
y = newy;
z = newz;
}
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);
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)
{
int origRequest = st->requestedBlotCount;
FLOAT tx, ty, tz, radius;
Blot *blots1, *blots2;
int count1, count2;
int n;
if (st->requestedBlotCount < 15)
{
/* special case bottom-out */
setupBlotsSphere (st);
return;
}
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)
{
/* return immediately if this satisfies the original count request */
st->requestedBlotCount = origRequest;
return;
}
blots1 = st->blots;
count1 = st->blotCount;
st->blots = NULL;
st->blotCount = 0;
/* translate to new position */
for (n = 0; n < count1; n++)
{
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 (struct state *st)
{
int which = RAND_FLOAT_01 * 11;
freeBlots (st);
switch (which)
{
case 0:
setupBlotsCube (st);
break;
case 1:
setupBlotsSphere (st);
break;
case 2:
setupBlotsCylinder (st);
break;
case 3:
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 */
st->segCount = st->blotCount * (blotShapeCount - 1);
st->segsToErase = calloc (sizeof (LineSegment), st->segCount);
st->segsToDraw = calloc (sizeof (LineSegment), st->segCount);
}
/*
* color setup stuff
*/
/* set up the colormap */
static void setupColormap (struct state *st, XWindowAttributes *xgwa)
{
int n;
XGCValues gcv;
XColor *colors = (XColor *) calloc (sizeof (XColor), st->colorCount + 1);
unsigned short r, g, b;
int h1, h2;
double s1, s2, v1, v2;
r = RAND_FLOAT_01 * 0x10000;
g = RAND_FLOAT_01 * 0x10000;
b = RAND_FLOAT_01 * 0x10000;
rgb_to_hsv (r, g, b, &h1, &s1, &v1);
v1 = 1.0;
s1 = 1.0;
r = RAND_FLOAT_01 * 0x10000;
g = RAND_FLOAT_01 * 0x10000;
b = RAND_FLOAT_01 * 0x10000;
rgb_to_hsv (r, g, b, &h2, &s2, &v2);
s2 = 0.7;
v2 = 0.7;
colors[0].pixel = get_pixel_resource (st->dpy, xgwa->colormap,
"background", "Background");
make_color_ramp (xgwa->screen, xgwa->visual, xgwa->colormap,
h1, s1, v1, h2, s2, v2,
colors + 1, &st->colorCount, False, True, False);
if (st->colorCount < 1)
{
fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
exit (-1);
}
st->gcs = (GC *) calloc (sizeof (GC), st->colorCount + 1);
for (n = 0; n <= st->colorCount; n++)
{
gcv.foreground = colors[n].pixel;
gcv.line_width = st->lineWidth;
st->gcs[n] = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth, &gcv);
}
free (colors);
}
/*
* overall setup stuff
*/
/* set up the system */
static void setup (struct state *st)
{
XWindowAttributes xgwa;
XGetWindowAttributes (st->dpy, st->window, &xgwa);
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 (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 */
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;
}
/*
* the simulation
*/
/* "render" the blots into segsToDraw, with the current rotation factors */
static void renderSegs (struct state *st)
{
int n;
int m = 0;
/* rotation factors */
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 = &st->blots[n];
int i, j;
int baseX, baseY;
FLOAT radius;
int x[3][3];
int y[3][3];
int color;
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;
/* the color to draw is based on the distance from the light of
* the post-rotation blot */
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 = st->colorCount;
}
/* set up the base screen coordinates for drawing */
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 * (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] * st->maxNerveRadius)) * radius;
y[i][j] = baseY +
((j - 1) + (b->yoff[i][j] * st->maxNerveRadius)) * radius;
}
}
for (i = 1; i < blotShapeCount; i++)
{
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 (struct state *st)
{
int n, i, j;
/* pick a new model if the time is right */
st->itersTillNext--;
if (st->itersTillNext < 0)
{
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 */
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 */
st->curScale = st->curScale + (st->scaleTarget - st->curScale) * st->iterAmt;
/* and similarly the light position */
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 < st->blotCount; n++)
{
/* add a bit of random jitter to xoff/yoff */
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
FLOAT newOff;
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;
st->blots[n].xoff[i][j] = newOff;
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;
st->blots[n].yoff[i][j] = newOff;
}
}
}
/* depending on random chance, update one or more factors */
if (RAND_FLOAT_01 <= st->eventChance)
{
int which = RAND_FLOAT_01 * 14;
switch (which)
{
case 0:
{
st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 1:
{
st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 2:
{
st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 3:
{
st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 4:
{
st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 5:
{
st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
break;
}
case 6:
{
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:
{
st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
break;
}
case 8:
{
st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
break;
}
case 9:
{
st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
break;
}
case 10:
{
st->scaleTarget =
RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
break;
}
case 11:
{
st->curScale =
RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
break;
}
case 12:
{
st->lightX = RAND_FLOAT_PM1;
st->lightY = RAND_FLOAT_PM1;
st->lightZ = RAND_FLOAT_PM1;
break;
}
case 13:
{
st->lightXTarget = RAND_FLOAT_PM1;
st->lightYTarget = RAND_FLOAT_PM1;
st->lightZTarget = RAND_FLOAT_PM1;
break;
}
}
}
}
/* erase segsToErase and draw segsToDraw */
static void eraseAndDraw (struct state *st)
{
int 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 = &st->segsToErase[n];
#ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */
XDrawLine (st->dpy, st->drawable, st->gcs[0],
seg->x1, seg->y1, seg->x2, seg->y2);
#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 unsigned long
nerverot_draw (Display *dpy, Window win, void *closure)
{
struct state *st = (struct state *) closure;
/* switch segsToErase and segsToDraw */
LineSegment *temp = st->segsToDraw;
st->segsToDraw = st->segsToErase;
st->segsToErase = temp;
/* update the model */
updateWithFeeling (st);
/* render new segments */
renderSegs (st);
/* erase old segments and draw new ones */
eraseAndDraw (st);
return st->delay;
}
/* initialize the user-specifiable params */
static void initParams (struct state *st)
{
int problems = 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_JWXYZ /* Don't second-guess Quartz's double-buffering */
st->doubleBuffer = False;
# endif
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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;
}
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 (st->maxScale < st->minScale)
{
fprintf (stderr, "error: maxScale must be >= minScale\n");
problems = 1;
}
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;
}
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 (st->maxRadius < st->minRadius)
{
fprintf (stderr, "error: maxRadius must be >= minRadius\n");
problems = 1;
}
if (problems)
{
exit (1);
}
}
static void *
nerverot_init (Display *dpy, Window window)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
st->dpy = dpy;
st->window = window;
initParams (st);
setup (st);
/* make a valid set to erase at first */
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;
int i;
freeBlots (st);
for (i = 0; i <= st->colorCount; i++)
XFreeGC (dpy, st->gcs[i]);
free (st->gcs);
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 HAVE_MOBILE
"*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)