1 /* nerverot, nervous rotation of random thingies, v1.0
2 * by Dan Bornstein, danfuzz@milk.com
3 * Copyright (c) 2000 Dan Bornstein.
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation. No representations are made about the suitability of this
10 * software for any purpose. It is provided "as is" without express or
13 * The goal of this screensaver is to be interesting and compelling to
14 * watch, yet induce a state of nervous edginess in the viewer.
16 * Brief description of options/resources:
18 * -fg <color>: foreground color
19 * -bg <color>: background color
20 * -delay <usec>: delay between frames
21 * -event-chance <frac>: chance, per iteration, that an interesting event
22 * will happen (range 0..1)
23 * -iter-amt <frac>: amount, per iteration, to move towards rotation and
24 * scale targets (range 0..1)
25 * -count <n>: number of blots
26 * -colors <n>: number of colors to use
27 * -lineWidth <n>: width of lines (0 means an optimized thin line)
28 * -nervousness <frac>: amount of nervousness (range 0..1)
29 * -min-scale <frac>: minimum scale of drawing as fraction of base scale
30 * (which is the minumum of the width or height of the screen) (range
32 * -max-scale <frac>: maximum scale of drawing as fraction of base scale
33 * (which is the minumum of the width or height of the screen) (range
35 * -min-radius <n>: minimum radius for drawing blots (range 1..100)
36 * -max-radius <n>: maximum radius for drawing blots (range 1..100)
37 * -max-nerve-radius <frac>: maximum nervousness radius (range 0..1)
41 #include "screenhack.h"
45 /* random float in the range (-1..1) */
46 #define RAND_FLOAT_PM1 \
47 (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
49 /* random float in the range (0..1) */
50 #define RAND_FLOAT_01 \
51 (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000))
55 /* parameters that are user configurable */
58 static int requestedBlotCount;
60 /* delay (usec) between iterations */
63 /* variability of xoff/yoff per iteration (0..1) */
64 static FLOAT nervousness;
66 /* max nervousness radius (0..1) */
67 static FLOAT maxNerveRadius;
69 /* chance per iteration that an event will happen */
70 static FLOAT eventChance;
72 /* fraction (0..1) towards rotation target or scale target to move each
76 /* min and max scale for drawing, as fraction of baseScale */
77 static FLOAT minScale;
78 static FLOAT maxScale;
80 /* min and max radius of blot drawing */
84 /* the number of colors to use */
85 static int colorCount;
92 /* non-user-modifiable immutable definitions */
94 /* base scale factor for drawing, calculated as
95 * max(screenWidth,screenHeight) */
98 /* width and height of the window */
99 static int windowWidth;
100 static int windowHeight;
102 /* center position of the window */
106 static Display *display; /* the display to draw on */
107 static Window window; /* the window to draw on */
108 static GC *gcs; /* array of gcs, one per color used */
112 /* structure of the model */
114 /* each point-like thingy to draw is represented as a blot */
115 typedef struct blot_s
117 FLOAT x; /* 3d x position (-1..1) */
118 FLOAT y; /* 3d y position (-1..1) */
119 FLOAT z; /* 3d z position (-1..1) */
120 FLOAT xoff[3][3]; /* display x offset per drawn point (-1..1) */
121 FLOAT yoff[3][3]; /* display x offset per drawn point (-1..1) */
124 /* each drawn line is represented as a LineSegment */
125 typedef struct linesegment_s
134 /* array of the blots in the model */
135 static Blot *blots = NULL;
136 static int blotCount;
138 /* each blot draws as a simple 2d shape with each coordinate as an int
139 * in the range (-1..1); this is the base shape */
140 static XPoint blotShape[] = { { 0, 0}, { 1, 0}, { 1, 1},
141 { 0, 1}, {-1, 1}, {-1, 0},
142 {-1,-1}, { 0,-1}, { 1,-1} };
143 static int blotShapeCount = sizeof (blotShape) / sizeof (XPoint);
145 /* two arrays of line segments; one for the ones to erase, and one for the
148 static LineSegment *segsToDraw = NULL;
149 static LineSegment *segsToErase = NULL;
151 /* current rotation values per axis, scale factor, and light position */
155 static FLOAT curScale;
160 /* target rotation values per axis, scale factor, and light position */
161 static FLOAT xRotTarget;
162 static FLOAT yRotTarget;
163 static FLOAT zRotTarget;
164 static FLOAT scaleTarget;
165 static FLOAT lightXTarget;
166 static FLOAT lightYTarget;
167 static FLOAT lightZTarget;
169 /* current absolute offsets from the center */
170 static int centerXOff = 0;
171 static int centerYOff = 0;
173 /* iterations until the model changes */
174 static int itersTillNext;
182 /* initialize a blot with the given coordinates and random display offsets */
183 static void initBlot (Blot *b, FLOAT x, FLOAT y, FLOAT z)
191 for (i = 0; i < 3; i++)
193 for (j = 0; j < 3; j++)
195 b->xoff[i][j] = RAND_FLOAT_PM1;
196 b->yoff[i][j] = RAND_FLOAT_PM1;
201 /* scale the blots to have a max distance of 1 from the center */
202 static void scaleBlotsToRadius1 (void)
207 for (n = 0; n < blotCount; n++)
210 blots[n].x * blots[n].x +
211 blots[n].y * blots[n].y +
212 blots[n].z * blots[n].z;
213 if (distSquare > max)
226 for (n = 0; n < blotCount; n++)
234 /* randomly reorder the blots */
235 static void randomlyReorderBlots (void)
239 for (n = 0; n < blotCount; n++)
241 int m = RAND_FLOAT_01 * (blotCount - n) + n;
242 Blot tmpBlot = blots[n];
248 /* set up the initial array of blots to be a at the edge of a sphere */
249 static void setupBlotsSphere (void)
253 blotCount = requestedBlotCount;
254 blots = calloc (sizeof (Blot), blotCount);
256 for (n = 0; n < blotCount; n++)
258 /* pick a spot, but reject if its radius is < 0.2 or > 1 to
259 * avoid scaling problems */
260 FLOAT x, y, z, radius;
268 radius = sqrt (x * x + y * y + z * z);
269 if ((radius >= 0.2) && (radius <= 1.0))
279 initBlot (&blots[n], x, y, z);
284 /* set up the initial array of blots to be a simple cube */
285 static void setupBlotsCube (void)
289 /* derive blotsPerEdge from blotCount, but then do the reverse
290 * since roundoff may have changed blotCount */
291 int blotsPerEdge = ((requestedBlotCount - 8) / 12) + 2;
294 if (blotsPerEdge < 2)
299 distBetween = 2.0 / (blotsPerEdge - 1.0);
301 blotCount = 8 + (blotsPerEdge - 2) * 12;
302 blots = calloc (sizeof (Blot), blotCount);
305 /* define the corners */
306 for (i = -1; i < 2; i += 2)
308 for (j = -1; j < 2; j += 2)
310 for (k = -1; k < 2; k += 2)
312 initBlot (&blots[n], i, j, k);
318 /* define the edges */
319 for (i = 1; i < (blotsPerEdge - 1); i++)
321 FLOAT varEdge = distBetween * i - 1;
322 initBlot (&blots[n++], varEdge, -1, -1);
323 initBlot (&blots[n++], varEdge, 1, -1);
324 initBlot (&blots[n++], varEdge, -1, 1);
325 initBlot (&blots[n++], varEdge, 1, 1);
326 initBlot (&blots[n++], -1, varEdge, -1);
327 initBlot (&blots[n++], 1, varEdge, -1);
328 initBlot (&blots[n++], -1, varEdge, 1);
329 initBlot (&blots[n++], 1, varEdge, 1);
330 initBlot (&blots[n++], -1, -1, varEdge);
331 initBlot (&blots[n++], 1, -1, varEdge);
332 initBlot (&blots[n++], -1, 1, varEdge);
333 initBlot (&blots[n++], 1, 1, varEdge);
336 scaleBlotsToRadius1 ();
337 randomlyReorderBlots ();
341 /* set up the initial array of blots to be a cylinder */
342 static void setupBlotsCylinder (void)
346 /* derive blotsPerEdge from blotCount, but then do the reverse
347 * since roundoff may have changed blotCount */
348 int blotsPerEdge = requestedBlotCount / 32;
351 if (blotsPerEdge < 2)
356 distBetween = 2.0 / (blotsPerEdge - 1);
358 blotCount = blotsPerEdge * 32;
359 blots = calloc (sizeof (Blot), blotCount);
362 /* define the edges */
363 for (i = 0; i < 32; i++)
365 FLOAT x = sin (2 * M_PI / 32 * i);
366 FLOAT y = cos (2 * M_PI / 32 * i);
367 for (j = 0; j < blotsPerEdge; j++)
369 initBlot (&blots[n], x, y, j * distBetween - 1);
374 scaleBlotsToRadius1 ();
375 randomlyReorderBlots ();
380 /* set up the initial array of blots to be a squiggle */
381 static void setupBlotsSquiggle (void)
383 FLOAT x, y, z, xv, yv, zv, len;
386 blotCount = requestedBlotCount;
387 blots = calloc (sizeof (Blot), blotCount);
396 len = sqrt (xv * xv + yv * yv + zv * zv);
401 for (n = 0; n < blotCount; n++)
403 FLOAT newx, newy, newz;
404 initBlot (&blots[n], x, y, z);
408 xv += RAND_FLOAT_PM1 * 0.1;
409 yv += RAND_FLOAT_PM1 * 0.1;
410 zv += RAND_FLOAT_PM1 * 0.1;
411 len = sqrt (xv * xv + yv * yv + zv * zv);
420 if ( (newx >= -1) && (newx <= 1)
421 && (newy >= -1) && (newy <= 1)
422 && (newz >= -1) && (newz <= 1))
433 scaleBlotsToRadius1 ();
434 randomlyReorderBlots ();
439 /* free the blots, in preparation for a new shape */
440 static void freeBlots (void)
448 if (segsToErase != NULL)
454 if (segsToDraw != NULL)
463 /* set up the initial arrays of blots */
464 static void setupBlots (void)
466 int which = RAND_FLOAT_01 * 4;
479 setupBlotsCylinder ();
482 setupBlotsSquiggle ();
486 /* there are blotShapeCount - 1 line segments per blot */
487 segCount = blotCount * (blotShapeCount - 1);
488 segsToErase = calloc (sizeof (LineSegment), segCount);
489 segsToDraw = calloc (sizeof (LineSegment), segCount);
491 /* erase the world */
492 XFillRectangle (display, window, gcs[0], 0, 0, windowWidth, windowHeight);
501 /* set up the colormap */
502 static void setupColormap (XWindowAttributes *xgwa)
506 XColor *colors = (XColor *) calloc (sizeof (XColor), colorCount + 1);
508 unsigned short r, g, b;
510 double s1, s2, v1, v2;
512 r = RAND_FLOAT_01 * 0x10000;
513 g = RAND_FLOAT_01 * 0x10000;
514 b = RAND_FLOAT_01 * 0x10000;
515 rgb_to_hsv (r, g, b, &h1, &s1, &v1);
519 r = RAND_FLOAT_01 * 0x10000;
520 g = RAND_FLOAT_01 * 0x10000;
521 b = RAND_FLOAT_01 * 0x10000;
522 rgb_to_hsv (r, g, b, &h2, &s2, &v2);
526 colors[0].pixel = get_pixel_resource ("background", "Background",
527 display, xgwa->colormap);
529 make_color_ramp (display, xgwa->colormap, h1, s1, v1, h2, s2, v2,
530 colors + 1, &colorCount, False, True, False);
534 fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
538 gcs = (GC *) calloc (sizeof (GC), colorCount + 1);
540 for (n = 0; n <= colorCount; n++)
542 gcv.foreground = colors[n].pixel;
543 gcv.line_width = lineWidth;
544 gcs[n] = XCreateGC (display, window, GCForeground | GCLineWidth, &gcv);
553 * overall setup stuff
556 /* set up the system */
557 static void setup (void)
559 XWindowAttributes xgwa;
561 XGetWindowAttributes (display, window, &xgwa);
563 windowWidth = xgwa.width;
564 windowHeight = xgwa.height;
565 centerX = windowWidth / 2;
566 centerY = windowHeight / 2;
567 baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
569 setupColormap (&xgwa);
572 /* set up the initial rotation, scale, and light values as random, but
573 * with the targets equal to where it is */
574 xRot = xRotTarget = RAND_FLOAT_01 * M_PI;
575 yRot = yRotTarget = RAND_FLOAT_01 * M_PI;
576 zRot = zRotTarget = RAND_FLOAT_01 * M_PI;
577 curScale = scaleTarget = RAND_FLOAT_01 * (maxScale - minScale) + minScale;
578 lightX = lightXTarget = RAND_FLOAT_PM1;
579 lightY = lightYTarget = RAND_FLOAT_PM1;
580 lightZ = lightZTarget = RAND_FLOAT_PM1;
582 itersTillNext = RAND_FLOAT_01 * 1234;
591 /* "render" the blots into segsToDraw, with the current rotation factors */
592 static void renderSegs (void)
597 /* rotation factors */
598 FLOAT sinX = sin (xRot);
599 FLOAT cosX = cos (xRot);
600 FLOAT sinY = sin (yRot);
601 FLOAT cosY = cos (yRot);
602 FLOAT sinZ = sin (zRot);
603 FLOAT cosZ = cos (zRot);
605 for (n = 0; n < blotCount; n++)
615 FLOAT x1 = blots[n].x;
616 FLOAT y1 = blots[n].y;
617 FLOAT z1 = blots[n].z;
620 /* rotate on z axis */
621 x2 = x1 * cosZ - y1 * sinZ;
622 y2 = x1 * sinZ + y1 * cosZ;
625 /* rotate on x axis */
626 y1 = y2 * cosX - z2 * sinX;
627 z1 = y2 * sinX + z2 * cosX;
630 /* rotate on y axis */
631 z2 = z1 * cosY - x1 * sinY;
632 x2 = z1 * sinY + x1 * cosY;
635 /* the color to draw is based on the distance from the light of
636 * the post-rotation blot */
640 color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * colorCount;
641 if (color > colorCount)
646 /* set up the base screen coordinates for drawing */
647 baseX = x2 / 2 * baseScale * curScale + centerX + centerXOff;
648 baseY = y2 / 2 * baseScale * curScale + centerY + centerYOff;
650 radius = (z2 + 1) / 2 * (maxRadius - minRadius) + minRadius;
652 for (i = 0; i < 3; i++)
654 for (j = 0; j < 3; j++)
657 ((i - 1) + (b->xoff[i][j] * maxNerveRadius)) * radius;
659 ((j - 1) + (b->yoff[i][j] * maxNerveRadius)) * radius;
663 for (i = 1; i < blotShapeCount; i++)
665 segsToDraw[m].gc = gcs[color];
666 segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
667 segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
668 segsToDraw[m].x2 = x[blotShape[i].x + 1][blotShape[i].y + 1];
669 segsToDraw[m].y2 = y[blotShape[i].x + 1][blotShape[i].y + 1];
675 /* update blots, adjusting the offsets and rotation factors. */
676 static void updateWithFeeling (void)
680 /* pick a new model if the time is right */
682 if (itersTillNext < 0)
684 itersTillNext = RAND_FLOAT_01 * 1234;
689 /* update the rotation factors by moving them a bit toward the targets */
690 xRot = xRot + (xRotTarget - xRot) * iterAmt;
691 yRot = yRot + (yRotTarget - yRot) * iterAmt;
692 zRot = zRot + (zRotTarget - zRot) * iterAmt;
694 /* similarly the scale factor */
695 curScale = curScale + (scaleTarget - curScale) * iterAmt;
697 /* and similarly the light position */
698 lightX = lightX + (lightXTarget - lightX) * iterAmt;
699 lightY = lightY + (lightYTarget - lightY) * iterAmt;
700 lightZ = lightZ + (lightZTarget - lightZ) * iterAmt;
702 /* for each blot... */
703 for (n = 0; n < blotCount; n++)
705 /* add a bit of random jitter to xoff/yoff */
706 for (i = 0; i < 3; i++)
708 for (j = 0; j < 3; j++)
712 newOff = blots[n].xoff[i][j] + RAND_FLOAT_PM1 * nervousness;
713 if (newOff < -1) newOff = -(newOff + 1) - 1;
714 else if (newOff > 1) newOff = -(newOff - 1) + 1;
715 blots[n].xoff[i][j] = newOff;
717 newOff = blots[n].yoff[i][j] + RAND_FLOAT_PM1 * nervousness;
718 if (newOff < -1) newOff = -(newOff + 1) - 1;
719 else if (newOff > 1) newOff = -(newOff - 1) + 1;
720 blots[n].yoff[i][j] = newOff;
725 /* depending on random chance, update one or more factors */
726 if (RAND_FLOAT_01 <= eventChance)
728 int which = RAND_FLOAT_01 * 14;
733 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
738 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
743 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
748 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
749 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
754 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
755 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
760 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
761 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
766 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
767 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
768 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
773 centerXOff = RAND_FLOAT_PM1 * maxRadius / 2;
778 centerYOff = RAND_FLOAT_PM1 * maxRadius / 2;
783 centerXOff = RAND_FLOAT_PM1 * maxRadius / 2;
784 centerYOff = RAND_FLOAT_PM1 * maxRadius / 2;
790 RAND_FLOAT_01 * (maxScale - minScale) + minScale;
796 RAND_FLOAT_01 * (maxScale - minScale) + minScale;
801 lightX = RAND_FLOAT_PM1;
802 lightY = RAND_FLOAT_PM1;
803 lightZ = RAND_FLOAT_PM1;
808 lightXTarget = RAND_FLOAT_PM1;
809 lightYTarget = RAND_FLOAT_PM1;
810 lightZTarget = RAND_FLOAT_PM1;
817 /* erase segsToErase and draw segsToDraw */
818 static void eraseAndDraw (void)
822 for (n = 0; n < segCount; n++)
824 LineSegment *seg = &segsToErase[n];
825 XDrawLine (display, window, gcs[0],
826 seg->x1, seg->y1, seg->x2, seg->y2);
827 seg = &segsToDraw[n];
828 XDrawLine (display, window, seg->gc,
829 seg->x1, seg->y1, seg->x2, seg->y2);
833 /* do one iteration */
834 static void oneIteration (void)
836 /* switch segsToErase and segsToDraw */
837 LineSegment *temp = segsToDraw;
838 segsToDraw = segsToErase;
841 /* update the model */
842 updateWithFeeling ();
844 /* render new segments */
847 /* erase old segments and draw new ones */
851 char *progclass = "NerveRot";
853 char *defaults [] = {
854 ".background: black",
855 ".foreground: white",
866 "*maxNerveRadius: 0.7",
871 XrmOptionDescRec options [] = {
872 { "-count", ".count", XrmoptionSepArg, 0 },
873 { "-colors", ".colors", XrmoptionSepArg, 0 },
874 { "-cube", ".cube", XrmoptionNoArg, "true" },
875 { "-delay", ".delay", XrmoptionSepArg, 0 },
876 { "-event-chance", ".eventChance", XrmoptionSepArg, 0 },
877 { "-iter-amt", ".iterAmt", XrmoptionSepArg, 0 },
878 { "-line-width", ".lineWidth", XrmoptionSepArg, 0 },
879 { "-min-scale", ".minScale", XrmoptionSepArg, 0 },
880 { "-max-scale", ".maxScale", XrmoptionSepArg, 0 },
881 { "-min-radius", ".minRadius", XrmoptionSepArg, 0 },
882 { "-max-radius", ".maxRadius", XrmoptionSepArg, 0 },
883 { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
884 { "-nervousness", ".nervousness", XrmoptionSepArg, 0 },
888 /* initialize the user-specifiable params */
889 static void initParams (void)
893 delay = get_integer_resource ("delay", "Delay");
896 fprintf (stderr, "error: delay must be at least 0\n");
900 requestedBlotCount = get_integer_resource ("count", "Count");
901 if (requestedBlotCount <= 0)
903 fprintf (stderr, "error: count must be at least 0\n");
907 colorCount = get_integer_resource ("colors", "Colors");
910 fprintf (stderr, "error: colors must be at least 1\n");
914 lineWidth = get_integer_resource ("lineWidth", "LineWidth");
917 fprintf (stderr, "error: line width must be at least 0\n");
921 nervousness = get_float_resource ("nervousness", "Float");
922 if ((nervousness < 0) || (nervousness > 1))
924 fprintf (stderr, "error: nervousness must be in the range 0..1\n");
928 maxNerveRadius = get_float_resource ("maxNerveRadius", "Float");
929 if ((maxNerveRadius < 0) || (maxNerveRadius > 1))
931 fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
935 eventChance = get_float_resource ("eventChance", "Float");
936 if ((eventChance < 0) || (eventChance > 1))
938 fprintf (stderr, "error: eventChance must be in the range 0..1\n");
942 iterAmt = get_float_resource ("iterAmt", "Float");
943 if ((iterAmt < 0) || (iterAmt > 1))
945 fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
949 minScale = get_float_resource ("minScale", "Float");
950 if ((minScale < 0) || (minScale > 10))
952 fprintf (stderr, "error: minScale must be in the range 0..10\n");
956 maxScale = get_float_resource ("maxScale", "Float");
957 if ((maxScale < 0) || (maxScale > 10))
959 fprintf (stderr, "error: maxScale must be in the range 0..10\n");
963 if (maxScale < minScale)
965 fprintf (stderr, "error: maxScale must be >= minScale\n");
969 minRadius = get_integer_resource ("minRadius", "Integer");
970 if ((minRadius < 1) || (minRadius > 100))
972 fprintf (stderr, "error: minRadius must be in the range 1..100\n");
976 maxRadius = get_integer_resource ("maxRadius", "Integer");
977 if ((maxRadius < 1) || (maxRadius > 100))
979 fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
983 if (maxRadius < minRadius)
985 fprintf (stderr, "error: maxRadius must be >= minRadius\n");
996 void screenhack (Display *dpy, Window win)
1004 /* make a valid set to erase at first */
1011 screenhack_handle_events (dpy);