/* 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 #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)