http://packetstormsecurity.org/UNIX/admin/xscreensaver-3.31.tar.gz
[xscreensaver] / hacks / nerverot.c
1 /* nerverot, nervous rotation of random thingies, v1.3
2  * by Dan Bornstein, danfuzz@milk.com
3  * Copyright (c) 2000-2001 Dan Bornstein. All rights reserved.
4  *
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 
11  * implied warranty.
12  *
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.
15  *
16  * See the included man page for more details.
17  */
18
19 #include <math.h>
20 #include "screenhack.h"
21
22 #define FLOAT double
23
24 /* random float in the range (-1..1) */
25 #define RAND_FLOAT_PM1 \
26         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
27
28 /* random float in the range (0..1) */
29 #define RAND_FLOAT_01 \
30         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
31
32
33
34 /* parameters that are user configurable */
35
36 /* number of blots */
37 static int requestedBlotCount;
38
39 /* delay (usec) between iterations */
40 int delay;
41
42 /* max iterations per model */
43 int maxIters;
44
45 /* variability of xoff/yoff per iteration (0..1) */
46 static FLOAT nervousness;
47
48 /* max nervousness radius (0..1) */
49 static FLOAT maxNerveRadius;
50
51 /* chance per iteration that an event will happen */
52 static FLOAT eventChance;
53
54 /* fraction (0..1) towards rotation target or scale target to move each
55  * iteration */
56 static FLOAT iterAmt;
57
58 /* min and max scale for drawing, as fraction of baseScale */
59 static FLOAT minScale;
60 static FLOAT maxScale;
61
62 /* min and max radius of blot drawing */
63 static int minRadius;
64 static int maxRadius;
65
66 /* the number of colors to use */
67 static int colorCount;
68
69 /* width of lines */
70 static int lineWidth;
71
72 /* whether or not to do double-buffering */
73 static Bool doubleBuffer;
74
75
76
77 /* non-user-modifiable immutable definitions */
78
79 /* base scale factor for drawing, calculated as
80  * max(screenWidth,screenHeight) */
81 static int baseScale;
82
83 /* width and height of the window */
84 static int windowWidth;
85 static int windowHeight;
86
87 /* center position of the window */
88 static int centerX;
89 static int centerY;
90
91 static Display *display;  /* the display to draw on */
92 static Window window;     /* the window to draw on */
93 static Drawable drawable; /* the thing to directly draw on */
94 static GC *gcs;           /* array of gcs, one per color used */
95
96
97
98 /* structure of the model */
99
100 /* each point-like thingy to draw is represented as a blot */
101 typedef struct blot_s
102 {
103     FLOAT x;           /* 3d x position (-1..1) */
104     FLOAT y;           /* 3d y position (-1..1) */
105     FLOAT z;           /* 3d z position (-1..1) */
106     FLOAT xoff[3][3];  /* display x offset per drawn point (-1..1) */
107     FLOAT yoff[3][3];  /* display x offset per drawn point (-1..1) */
108 } Blot;
109
110 /* each drawn line is represented as a LineSegment */
111 typedef struct linesegment_s
112 {
113     GC gc;
114     int x1;
115     int y1;
116     int x2;
117     int y2;
118 } LineSegment;
119
120 /* array of the blots in the model */
121 static Blot *blots = NULL;
122 static int blotCount;
123
124 /* each blot draws as a simple 2d shape with each coordinate as an int
125  * in the range (-1..1); this is the base shape */
126 static XPoint blotShape[] = { { 0, 0}, { 1, 0}, { 1, 1}, 
127                               { 0, 1}, {-1, 1}, {-1, 0}, 
128                               {-1,-1}, { 0,-1}, { 1,-1} };
129 static int blotShapeCount = sizeof (blotShape) / sizeof (XPoint);
130
131 /* two arrays of line segments; one for the ones to erase, and one for the
132  * ones to draw */
133 static int segCount;
134 static LineSegment *segsToDraw = NULL;
135 static LineSegment *segsToErase = NULL;
136
137 /* current rotation values per axis, scale factor, and light position */
138 static FLOAT xRot;
139 static FLOAT yRot;
140 static FLOAT zRot;
141 static FLOAT curScale;
142 static FLOAT lightX;
143 static FLOAT lightY;
144 static FLOAT lightZ;
145
146 /* target rotation values per axis, scale factor, and light position */
147 static FLOAT xRotTarget;
148 static FLOAT yRotTarget;
149 static FLOAT zRotTarget;
150 static FLOAT scaleTarget;
151 static FLOAT lightXTarget;
152 static FLOAT lightYTarget;
153 static FLOAT lightZTarget;
154
155 /* current absolute offsets from the center */
156 static int centerXOff = 0;
157 static int centerYOff = 0;
158
159 /* iterations until the model changes */
160 static int itersTillNext;
161
162
163
164 /*
165  * blot setup stuff
166  */
167
168 /* initialize a blot with the given coordinates and random display offsets */
169 static void initBlot (Blot *b, FLOAT x, FLOAT y, FLOAT z)
170 {
171     int i, j;
172
173     b->x = x;
174     b->y = y;
175     b->z = z;
176
177     for (i = 0; i < 3; i++)
178     {
179         for (j = 0; j < 3; j++)
180         {
181             b->xoff[i][j] = RAND_FLOAT_PM1;
182             b->yoff[i][j] = RAND_FLOAT_PM1;
183         }
184     }
185 }
186
187 /* scale the blots to have a max distance of 1 from the center */
188 static void scaleBlotsToRadius1 (void)
189 {
190     FLOAT max = 0.0;
191     int n;
192
193     for (n = 0; n < blotCount; n++)
194     {
195         FLOAT distSquare = 
196             blots[n].x * blots[n].x +
197             blots[n].y * blots[n].y +
198             blots[n].z * blots[n].z;
199         if (distSquare > max)
200         {
201             max = distSquare;
202         }
203     }
204
205     if (max == 0.0)
206     {
207         return;
208     }
209
210     max = sqrt (max);
211
212     for (n = 0; n < blotCount; n++)
213     {
214         blots[n].x /= max;
215         blots[n].y /= max;
216         blots[n].z /= max;
217     }
218 }
219
220 /* randomly reorder the blots */
221 static void randomlyReorderBlots (void)
222 {
223     int n;
224
225     for (n = 0; n < blotCount; n++)
226     {
227         int m = RAND_FLOAT_01 * (blotCount - n) + n;
228         Blot tmpBlot = blots[n];
229         blots[n] = blots[m];
230         blots[m] = tmpBlot;
231     }
232 }
233
234 /* set up the initial array of blots to be a at the edge of a sphere */
235 static void setupBlotsSphere (void)
236 {
237     int n;
238
239     blotCount = requestedBlotCount;
240     blots = calloc (sizeof (Blot), blotCount);
241
242     for (n = 0; n < blotCount; n++)
243     {
244         /* pick a spot, but reject if its radius is < 0.2 or > 1 to
245          * avoid scaling problems */
246         FLOAT x, y, z, radius;
247
248         for (;;)
249         {
250             x = RAND_FLOAT_PM1;
251             y = RAND_FLOAT_PM1;
252             z = RAND_FLOAT_PM1;
253
254             radius = sqrt (x * x + y * y + z * z);
255             if ((radius >= 0.2) && (radius <= 1.0))
256             {
257                 break;
258             }
259         }
260
261         x /= radius;
262         y /= radius;
263         z /= radius;
264
265         initBlot (&blots[n], x, y, z);
266     }
267 }
268
269
270
271 /* set up the initial array of blots to be a simple cube */
272 static void setupBlotsCube (void)
273 {
274     int i, j, k, n;
275
276     /* derive blotsPerEdge from blotCount, but then do the reverse
277      * since roundoff may have changed blotCount */
278     int blotsPerEdge = ((requestedBlotCount - 8) / 12) + 2;
279     FLOAT distBetween;
280
281     if (blotsPerEdge < 2)
282     {
283         blotsPerEdge = 2;
284     }
285
286     distBetween = 2.0 / (blotsPerEdge - 1.0);
287
288     blotCount = 8 + (blotsPerEdge - 2) * 12;
289     blots = calloc (sizeof (Blot), blotCount);
290     n = 0;
291
292     /* define the corners */
293     for (i = -1; i < 2; i += 2)
294     {
295         for (j = -1; j < 2; j += 2)
296         {
297             for (k = -1; k < 2; k += 2)
298             {
299                 initBlot (&blots[n], i, j, k);
300                 n++;
301             } 
302         }
303     }
304
305     /* define the edges */
306     for (i = 1; i < (blotsPerEdge - 1); i++)
307     {
308         FLOAT varEdge = distBetween * i - 1;
309         initBlot (&blots[n++], varEdge, -1, -1);
310         initBlot (&blots[n++], varEdge,  1, -1);
311         initBlot (&blots[n++], varEdge, -1,  1);
312         initBlot (&blots[n++], varEdge,  1,  1);
313         initBlot (&blots[n++], -1, varEdge, -1);
314         initBlot (&blots[n++],  1, varEdge, -1);
315         initBlot (&blots[n++], -1, varEdge,  1);
316         initBlot (&blots[n++],  1, varEdge,  1);
317         initBlot (&blots[n++], -1, -1, varEdge);
318         initBlot (&blots[n++],  1, -1, varEdge);
319         initBlot (&blots[n++], -1,  1, varEdge);
320         initBlot (&blots[n++],  1,  1, varEdge);
321     }
322
323     scaleBlotsToRadius1 ();
324     randomlyReorderBlots ();
325 }
326
327
328
329 /* set up the initial array of blots to be a cylinder */
330 static void setupBlotsCylinder (void)
331 {
332     int i, j, n;
333
334     /* derive blotsPerEdge from blotCount, but then do the reverse
335      * since roundoff may have changed blotCount */
336     int blotsPerEdge = requestedBlotCount / 32;
337     FLOAT distBetween;
338
339     if (blotsPerEdge < 2)
340     {
341         blotsPerEdge = 2;
342     }
343
344     distBetween = 2.0 / (blotsPerEdge - 1);
345
346     blotCount = blotsPerEdge * 32;
347     blots = calloc (sizeof (Blot), blotCount);
348     n = 0;
349
350     /* define the edges */
351     for (i = 0; i < 32; i++)
352     {
353         FLOAT x = sin (2 * M_PI / 32 * i);
354         FLOAT y = cos (2 * M_PI / 32 * i);
355         for (j = 0; j < blotsPerEdge; j++)
356         {
357             initBlot (&blots[n], x, y, j * distBetween - 1);
358             n++;
359         }
360     }
361
362     scaleBlotsToRadius1 ();
363     randomlyReorderBlots ();
364 }
365
366
367
368 /* set up the initial array of blots to be a squiggle */
369 static void setupBlotsSquiggle (void)
370 {
371     FLOAT x, y, z, xv, yv, zv, len;
372     int minCoor, maxCoor;
373     int n;
374
375     blotCount = requestedBlotCount;
376     blots = calloc (sizeof (Blot), blotCount);
377
378     maxCoor = (int) (RAND_FLOAT_01 * 5) + 1;
379     minCoor = -maxCoor;
380
381     x = RAND_FLOAT_PM1;
382     y = RAND_FLOAT_PM1;
383     z = RAND_FLOAT_PM1;
384
385     xv = RAND_FLOAT_PM1;
386     yv = RAND_FLOAT_PM1;
387     zv = RAND_FLOAT_PM1;
388     len = sqrt (xv * xv + yv * yv + zv * zv);
389     xv /= len;
390     yv /= len;
391     zv /= len;
392     
393     for (n = 0; n < blotCount; n++)
394     {
395         FLOAT newx, newy, newz;
396         initBlot (&blots[n], x, y, z);
397
398         for (;;)
399         {
400             xv += RAND_FLOAT_PM1 * 0.1;
401             yv += RAND_FLOAT_PM1 * 0.1;
402             zv += RAND_FLOAT_PM1 * 0.1;
403             len = sqrt (xv * xv + yv * yv + zv * zv);
404             xv /= len;
405             yv /= len;
406             zv /= len;
407
408             newx = x + xv * 0.1;
409             newy = y + yv * 0.1;
410             newz = z + zv * 0.1;
411
412             if (   (newx >= minCoor) && (newx <= maxCoor)
413                 && (newy >= minCoor) && (newy <= maxCoor)
414                 && (newz >= minCoor) && (newz <= maxCoor))
415             {
416                 break;
417             }
418         }
419
420         x = newx;
421         y = newy;
422         z = newz;
423     }
424
425     scaleBlotsToRadius1 ();
426     randomlyReorderBlots ();
427 }
428
429
430
431 /* set up the initial array of blots to be near the corners of a
432  * cube, distributed slightly */
433 static void setupBlotsCubeCorners (void)
434 {
435     int n;
436
437     blotCount = requestedBlotCount;
438     blots = calloc (sizeof (Blot), blotCount);
439
440     for (n = 0; n < blotCount; n++)
441     {
442         FLOAT x = rint (RAND_FLOAT_01) * 2 - 1;
443         FLOAT y = rint (RAND_FLOAT_01) * 2 - 1;
444         FLOAT z = rint (RAND_FLOAT_01) * 2 - 1;
445
446         x += RAND_FLOAT_PM1 * 0.3;
447         y += RAND_FLOAT_PM1 * 0.3;
448         z += RAND_FLOAT_PM1 * 0.3;
449
450         initBlot (&blots[n], x, y, z);
451     }
452
453     scaleBlotsToRadius1 ();
454 }
455
456
457
458 /* set up the initial array of blots to be randomly distributed
459  * on the surface of a tetrahedron */
460 static void setupBlotsTetrahedron (void)
461 {
462     /* table of corners of the tetrahedron */
463     static FLOAT cor[4][3] = { {  0.0,   1.0,  0.0 },
464                                { -0.75, -0.5, -0.433013 },
465                                {  0.0,  -0.5,  0.866025 },
466                                {  0.75, -0.5, -0.433013 } };
467
468     int n, c;
469
470     /* derive blotsPerSurface from blotCount, but then do the reverse
471      * since roundoff may have changed blotCount */
472     int blotsPerSurface = requestedBlotCount / 4;
473
474     blotCount = blotsPerSurface * 4;
475     blots = calloc (sizeof (Blot), blotCount);
476
477     for (n = 0; n < blotCount; n += 4)
478     {
479         /* pick a random point on a unit right triangle */
480         FLOAT rawx = RAND_FLOAT_01;
481         FLOAT rawy = RAND_FLOAT_01;
482
483         if ((rawx + rawy) > 1)
484         {
485             /* swap coords into place */
486             FLOAT t = 1.0 - rawx;
487             rawx = 1.0 - rawy;
488             rawy = t;
489         }
490
491         /* translate the point to be on each of the surfaces */
492         for (c = 0; c < 4; c++)
493         {
494             FLOAT x, y, z;
495             
496             int c1 = (c + 1) % 4;
497             int c2 = (c + 2) % 4;
498             
499             x = (cor[c1][0] - cor[c][0]) * rawx + 
500                 (cor[c2][0] - cor[c][0]) * rawy + 
501                 cor[c][0];
502
503             y = (cor[c1][1] - cor[c][1]) * rawx + 
504                 (cor[c2][1] - cor[c][1]) * rawy + 
505                 cor[c][1];
506
507             z = (cor[c1][2] - cor[c][2]) * rawx + 
508                 (cor[c2][2] - cor[c][2]) * rawy + 
509                 cor[c][2];
510
511             initBlot (&blots[n + c], x, y, z);
512         }
513     }
514 }
515
516
517
518 /* forward declaration for recursive use immediately below */
519 static void setupBlots (void);
520
521 /* set up the blots to be two of the other choices, placed next to
522  * each other */
523 static void setupBlotsDuo (void)
524 {
525     int origRequest = requestedBlotCount;
526     FLOAT tx, ty, tz, radius;
527     Blot *blots1, *blots2;
528     int count1, count2;
529     int n;
530
531     if (requestedBlotCount < 15)
532     {
533         /* special case bottom-out */
534         setupBlotsSphere ();
535         return;
536     }
537
538     tx = RAND_FLOAT_PM1;
539     ty = RAND_FLOAT_PM1;
540     tz = RAND_FLOAT_PM1;
541     radius = sqrt (tx * tx + ty * ty + tz * tz);
542     tx /= radius;
543     ty /= radius;
544     tz /= radius;
545
546     /* recursive call to setup set 1 */
547     requestedBlotCount = origRequest / 2;
548     setupBlots ();
549
550     if (blotCount >= origRequest)
551     {
552         /* return immediately if this satisfies the original count request */
553         requestedBlotCount = origRequest;
554         return;
555     }
556
557     blots1 = blots;
558     count1 = blotCount;
559     blots = NULL;
560     blotCount = 0;
561     
562     /* translate to new position */
563     for (n = 0; n < count1; n++)
564     {
565         blots1[n].x += tx;
566         blots1[n].y += ty;
567         blots1[n].z += tz;
568     }
569
570     /* recursive call to setup set 2 */
571     requestedBlotCount = origRequest - count1;
572     setupBlots ();
573     blots2 = blots;
574     count2 = blotCount;
575
576     /* translate to new position */
577     for (n = 0; n < count2; n++)
578     {
579         blots2[n].x -= tx;
580         blots2[n].y -= ty;
581         blots2[n].z -= tz;
582     }
583
584     /* combine the two arrays */
585     blotCount = count1 + count2;
586     blots = calloc (sizeof (Blot), blotCount);
587     memcpy (&blots[0],      blots1, sizeof (Blot) * count1);
588     memcpy (&blots[count1], blots2, sizeof (Blot) * count2);
589     free (blots1);
590     free (blots2);
591
592     scaleBlotsToRadius1 ();
593     randomlyReorderBlots ();
594
595     /* restore the original requested count, for future iterations */
596     requestedBlotCount = origRequest;
597 }
598
599
600
601 /* free the blots, in preparation for a new shape */
602 static void freeBlots (void)
603 {
604     if (blots != NULL)
605     {
606         free (blots);
607         blots = NULL;
608     }
609
610     if (segsToErase != NULL)
611     {
612         free (segsToErase);
613         segsToErase = NULL;
614     }
615
616     if (segsToDraw != NULL)
617     {
618         free (segsToDraw);
619         segsToDraw = NULL;
620     }
621 }
622
623
624
625 /* set up the initial arrays of blots */
626 static void setupBlots (void)
627 {
628     int which = RAND_FLOAT_01 * 8;
629
630     freeBlots ();
631
632     switch (which)
633     {
634         case 0:
635             setupBlotsCube ();
636             break;
637         case 1:
638             setupBlotsSphere ();
639             break;
640         case 2:
641             setupBlotsCylinder ();
642             break;
643         case 3:
644             setupBlotsSquiggle ();
645             break;
646         case 4:
647             setupBlotsCubeCorners ();
648             break;
649         case 5:
650             setupBlotsTetrahedron ();
651             break;
652         case 6:
653         case 7:
654             setupBlotsDuo ();
655             break;
656     }
657 }
658
659
660
661 /* set up the segments arrays */
662 static void setupSegs (void)
663 {
664     /* there are blotShapeCount - 1 line segments per blot */
665     segCount = blotCount * (blotShapeCount - 1);
666     segsToErase = calloc (sizeof (LineSegment), segCount);
667     segsToDraw = calloc (sizeof (LineSegment), segCount);
668
669     /* erase the world */
670     XFillRectangle (display, drawable, gcs[0], 0, 0, 
671                     windowWidth, windowHeight);
672 }
673
674
675
676 /*
677  * color setup stuff
678  */
679
680 /* set up the colormap */
681 static void setupColormap (XWindowAttributes *xgwa)
682 {
683     int n;
684     XGCValues gcv;
685     XColor *colors = (XColor *) calloc (sizeof (XColor), colorCount + 1);
686
687     unsigned short r, g, b;
688     int h1, h2;
689     double s1, s2, v1, v2;
690
691     r = RAND_FLOAT_01 * 0x10000;
692     g = RAND_FLOAT_01 * 0x10000;
693     b = RAND_FLOAT_01 * 0x10000;
694     rgb_to_hsv (r, g, b, &h1, &s1, &v1);
695     v1 = 1.0;
696     s1 = 1.0;
697
698     r = RAND_FLOAT_01 * 0x10000;
699     g = RAND_FLOAT_01 * 0x10000;
700     b = RAND_FLOAT_01 * 0x10000;
701     rgb_to_hsv (r, g, b, &h2, &s2, &v2);
702     s2 = 0.7;
703     v2 = 0.7;
704     
705     colors[0].pixel = get_pixel_resource ("background", "Background",
706                                           display, xgwa->colormap);
707     
708     make_color_ramp (display, xgwa->colormap, h1, s1, v1, h2, s2, v2,
709                      colors + 1, &colorCount, False, True, False);
710
711     if (colorCount < 1)
712     {
713         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
714         exit (-1);
715     }
716     
717     gcs = (GC *) calloc (sizeof (GC), colorCount + 1);
718
719     for (n = 0; n <= colorCount; n++) 
720     {
721         gcv.foreground = colors[n].pixel;
722         gcv.line_width = lineWidth;
723         gcs[n] = XCreateGC (display, window, GCForeground | GCLineWidth, &gcv);
724     }
725
726     free (colors);
727 }
728
729
730
731 /*
732  * overall setup stuff
733  */
734
735 /* set up the system */
736 static void setup (void)
737 {
738     XWindowAttributes xgwa;
739
740     XGetWindowAttributes (display, window, &xgwa);
741
742     windowWidth = xgwa.width;
743     windowHeight = xgwa.height;
744     centerX = windowWidth / 2;
745     centerY = windowHeight / 2;
746     baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
747
748     if (doubleBuffer)
749     {
750         drawable = XCreatePixmap (display, window, xgwa.width, xgwa.height,
751                                   xgwa.depth);
752     }
753     else
754     {
755         drawable = window;
756     }
757
758     setupColormap (&xgwa);
759     setupBlots ();
760     setupSegs ();
761
762     /* set up the initial rotation, scale, and light values as random, but
763      * with the targets equal to where it is */
764     xRot = xRotTarget = RAND_FLOAT_01 * M_PI;
765     yRot = yRotTarget = RAND_FLOAT_01 * M_PI;
766     zRot = zRotTarget = RAND_FLOAT_01 * M_PI;
767     curScale = scaleTarget = RAND_FLOAT_01 * (maxScale - minScale) + minScale;
768     lightX = lightXTarget = RAND_FLOAT_PM1;
769     lightY = lightYTarget = RAND_FLOAT_PM1;
770     lightZ = lightZTarget = RAND_FLOAT_PM1;
771
772     itersTillNext = RAND_FLOAT_01 * maxIters;
773 }
774
775
776
777 /*
778  * the simulation
779  */
780
781 /* "render" the blots into segsToDraw, with the current rotation factors */
782 static void renderSegs (void)
783 {
784     int n;
785     int m = 0;
786
787     /* rotation factors */
788     FLOAT sinX = sin (xRot);
789     FLOAT cosX = cos (xRot);
790     FLOAT sinY = sin (yRot);
791     FLOAT cosY = cos (yRot);
792     FLOAT sinZ = sin (zRot);
793     FLOAT cosZ = cos (zRot);
794
795     for (n = 0; n < blotCount; n++)
796     {
797         Blot *b = &blots[n];
798         int i, j;
799         int baseX, baseY;
800         FLOAT radius;
801         int x[3][3];
802         int y[3][3];
803         int color;
804
805         FLOAT x1 = blots[n].x;
806         FLOAT y1 = blots[n].y;
807         FLOAT z1 = blots[n].z;
808         FLOAT x2, y2, z2;
809
810         /* rotate on z axis */
811         x2 = x1 * cosZ - y1 * sinZ;
812         y2 = x1 * sinZ + y1 * cosZ;
813         z2 = z1;
814
815         /* rotate on x axis */
816         y1 = y2 * cosX - z2 * sinX;
817         z1 = y2 * sinX + z2 * cosX;
818         x1 = x2;
819
820         /* rotate on y axis */
821         z2 = z1 * cosY - x1 * sinY;
822         x2 = z1 * sinY + x1 * cosY;
823         y2 = y1;
824
825         /* the color to draw is based on the distance from the light of
826          * the post-rotation blot */
827         x1 = x2 - lightX;
828         y1 = y2 - lightY;
829         z1 = z2 - lightZ;
830         color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * colorCount;
831         if (color > colorCount)
832         {
833             color = colorCount;
834         }
835
836         /* set up the base screen coordinates for drawing */
837         baseX = x2 / 2 * baseScale * curScale + centerX + centerXOff;
838         baseY = y2 / 2 * baseScale * curScale + centerY + centerYOff;
839         
840         radius = (z2 + 1) / 2 * (maxRadius - minRadius) + minRadius;
841
842         for (i = 0; i < 3; i++)
843         {
844             for (j = 0; j < 3; j++)
845             {
846                 x[i][j] = baseX + 
847                     ((i - 1) + (b->xoff[i][j] * maxNerveRadius)) * radius;
848                 y[i][j] = baseY + 
849                     ((j - 1) + (b->yoff[i][j] * maxNerveRadius)) * radius;
850             }
851         }
852
853         for (i = 1; i < blotShapeCount; i++)
854         {
855             segsToDraw[m].gc = gcs[color];
856             segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
857             segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
858             segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
859             segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
860             m++;
861         }
862     }
863 }
864
865 /* update blots, adjusting the offsets and rotation factors. */
866 static void updateWithFeeling (void)
867 {
868     int n, i, j;
869
870     /* pick a new model if the time is right */
871     itersTillNext--;
872     if (itersTillNext < 0)
873     {
874         itersTillNext = RAND_FLOAT_01 * maxIters;
875         setupBlots ();
876         setupSegs ();
877         renderSegs ();
878     }
879
880     /* update the rotation factors by moving them a bit toward the targets */
881     xRot = xRot + (xRotTarget - xRot) * iterAmt; 
882     yRot = yRot + (yRotTarget - yRot) * iterAmt;
883     zRot = zRot + (zRotTarget - zRot) * iterAmt;
884
885     /* similarly the scale factor */
886     curScale = curScale + (scaleTarget - curScale) * iterAmt;
887
888     /* and similarly the light position */
889     lightX = lightX + (lightXTarget - lightX) * iterAmt; 
890     lightY = lightY + (lightYTarget - lightY) * iterAmt; 
891     lightZ = lightZ + (lightZTarget - lightZ) * iterAmt; 
892
893     /* for each blot... */
894     for (n = 0; n < blotCount; n++)
895     {
896         /* add a bit of random jitter to xoff/yoff */
897         for (i = 0; i < 3; i++)
898         {
899             for (j = 0; j < 3; j++)
900             {
901                 FLOAT newOff;
902
903                 newOff = blots[n].xoff[i][j] + RAND_FLOAT_PM1 * nervousness;
904                 if (newOff < -1) newOff = -(newOff + 1) - 1;
905                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
906                 blots[n].xoff[i][j] = newOff;
907
908                 newOff = blots[n].yoff[i][j] + RAND_FLOAT_PM1 * nervousness;
909                 if (newOff < -1) newOff = -(newOff + 1) - 1;
910                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
911                 blots[n].yoff[i][j] = newOff;
912             }
913         }
914     }
915
916     /* depending on random chance, update one or more factors */
917     if (RAND_FLOAT_01 <= eventChance)
918     {
919         int which = RAND_FLOAT_01 * 14;
920         switch (which)
921         {
922             case 0:
923             {
924                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
925                 break;
926             }
927             case 1:
928             {
929                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
930                 break;
931             }
932             case 2:
933             {
934                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
935                 break;
936             }
937             case 3:
938             {
939                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
940                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
941                 break;
942             }
943             case 4:
944             {
945                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
946                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
947                 break;
948             }
949             case 5:
950             {
951                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
952                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
953                 break;
954             }
955             case 6:
956             {
957                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
958                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
959                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
960                 break;
961             }
962             case 7:
963             {
964                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
965                 break;
966             }
967             case 8:
968             {
969                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
970                 break;
971             }
972             case 9:
973             {
974                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
975                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
976                 break;
977             }
978             case 10:
979             {
980                 scaleTarget = 
981                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
982                 break;
983             }
984             case 11:
985             {
986                 curScale = 
987                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
988                 break;
989             }
990             case 12:
991             {
992                 lightX = RAND_FLOAT_PM1;
993                 lightY = RAND_FLOAT_PM1;
994                 lightZ = RAND_FLOAT_PM1;
995                 break;
996             }
997             case 13:
998             {
999                 lightXTarget = RAND_FLOAT_PM1;
1000                 lightYTarget = RAND_FLOAT_PM1;
1001                 lightZTarget = RAND_FLOAT_PM1;
1002                 break;
1003             }
1004         }
1005     }
1006 }
1007
1008 /* erase segsToErase and draw segsToDraw */
1009 static void eraseAndDraw (void)
1010 {
1011     int n;
1012
1013     for (n = 0; n < segCount; n++)
1014     {
1015         LineSegment *seg = &segsToErase[n];
1016         XDrawLine (display, drawable, gcs[0], 
1017                    seg->x1, seg->y1, seg->x2, seg->y2);
1018         seg = &segsToDraw[n];
1019         XDrawLine (display, drawable, seg->gc,
1020                    seg->x1, seg->y1, seg->x2, seg->y2);
1021     }
1022
1023     if (doubleBuffer)
1024     {
1025         XCopyArea (display, drawable, window, gcs[0], 0, 0, 
1026                    windowWidth, windowHeight, 0, 0);
1027     }
1028 }
1029
1030 /* do one iteration */
1031 static void oneIteration (void)
1032 {
1033     /* switch segsToErase and segsToDraw */
1034     LineSegment *temp = segsToDraw;
1035     segsToDraw = segsToErase;
1036     segsToErase = temp;
1037
1038     /* update the model */
1039     updateWithFeeling ();
1040
1041     /* render new segments */
1042     renderSegs ();
1043
1044     /* erase old segments and draw new ones */
1045     eraseAndDraw ();
1046 }
1047
1048 char *progclass = "NerveRot";
1049
1050 char *defaults [] = {
1051     ".background:       black",
1052     ".foreground:       white",
1053     "*count:            250",
1054     "*colors:           4",
1055     "*delay:            10000",
1056     "*maxIters:         1200",
1057     "*doubleBuffer:     false",
1058     "*eventChance:      0.2",
1059     "*iterAmt:          0.01",
1060     "*lineWidth:        0",
1061     "*minScale:         0.6",
1062     "*maxScale:         1.75",
1063     "*minRadius:        3",
1064     "*maxRadius:        25",
1065     "*maxNerveRadius:   0.7",
1066     "*nervousness:      0.3",
1067     0
1068 };
1069
1070 XrmOptionDescRec options [] = {
1071   { "-count",            ".count",          XrmoptionSepArg, 0 },
1072   { "-colors",           ".colors",         XrmoptionSepArg, 0 },
1073   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
1074   { "-max-iters",        ".maxIters",       XrmoptionSepArg, 0 },
1075   { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
1076   { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
1077   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
1078   { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
1079   { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
1080   { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
1081   { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
1082   { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
1083   { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
1084   { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
1085   { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
1086   { 0, 0, 0, 0 }
1087 };
1088
1089 /* initialize the user-specifiable params */
1090 static void initParams (void)
1091 {
1092     int problems = 0;
1093
1094     delay = get_integer_resource ("delay", "Delay");
1095     if (delay < 0)
1096     {
1097         fprintf (stderr, "error: delay must be at least 0\n");
1098         problems = 1;
1099     }
1100     
1101     maxIters = get_integer_resource ("maxIters", "Integer");
1102     if (maxIters < 0)
1103     {
1104         fprintf (stderr, "error: maxIters must be at least 0\n");
1105         problems = 1;
1106     }
1107     
1108     doubleBuffer = get_boolean_resource ("doubleBuffer", "Boolean");
1109
1110     requestedBlotCount = get_integer_resource ("count", "Count");
1111     if (requestedBlotCount <= 0)
1112     {
1113         fprintf (stderr, "error: count must be at least 0\n");
1114         problems = 1;
1115     }
1116
1117     colorCount = get_integer_resource ("colors", "Colors");
1118     if (colorCount <= 0)
1119     {
1120         fprintf (stderr, "error: colors must be at least 1\n");
1121         problems = 1;
1122     }
1123
1124     lineWidth = get_integer_resource ("lineWidth", "LineWidth");
1125     if (lineWidth < 0)
1126     {
1127         fprintf (stderr, "error: line width must be at least 0\n");
1128         problems = 1;
1129     }
1130
1131     nervousness = get_float_resource ("nervousness", "Float");
1132     if ((nervousness < 0) || (nervousness > 1))
1133     {
1134         fprintf (stderr, "error: nervousness must be in the range 0..1\n");
1135         problems = 1;
1136     }
1137
1138     maxNerveRadius = get_float_resource ("maxNerveRadius", "Float");
1139     if ((maxNerveRadius < 0) || (maxNerveRadius > 1))
1140     {
1141         fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
1142         problems = 1;
1143     }
1144
1145     eventChance = get_float_resource ("eventChance", "Float");
1146     if ((eventChance < 0) || (eventChance > 1))
1147     {
1148         fprintf (stderr, "error: eventChance must be in the range 0..1\n");
1149         problems = 1;
1150     }
1151
1152     iterAmt = get_float_resource ("iterAmt", "Float");
1153     if ((iterAmt < 0) || (iterAmt > 1))
1154     {
1155         fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
1156         problems = 1;
1157     }
1158
1159     minScale = get_float_resource ("minScale", "Float");
1160     if ((minScale < 0) || (minScale > 10))
1161     {
1162         fprintf (stderr, "error: minScale must be in the range 0..10\n");
1163         problems = 1;
1164     }
1165
1166     maxScale = get_float_resource ("maxScale", "Float");
1167     if ((maxScale < 0) || (maxScale > 10))
1168     {
1169         fprintf (stderr, "error: maxScale must be in the range 0..10\n");
1170         problems = 1;
1171     }
1172
1173     if (maxScale < minScale)
1174     {
1175         fprintf (stderr, "error: maxScale must be >= minScale\n");
1176         problems = 1;
1177     }   
1178
1179     minRadius = get_integer_resource ("minRadius", "Integer");
1180     if ((minRadius < 1) || (minRadius > 100))
1181     {
1182         fprintf (stderr, "error: minRadius must be in the range 1..100\n");
1183         problems = 1;
1184     }
1185
1186     maxRadius = get_integer_resource ("maxRadius", "Integer");
1187     if ((maxRadius < 1) || (maxRadius > 100))
1188     {
1189         fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
1190         problems = 1;
1191     }
1192
1193     if (maxRadius < minRadius)
1194     {
1195         fprintf (stderr, "error: maxRadius must be >= minRadius\n");
1196         problems = 1;
1197     }   
1198
1199     if (problems)
1200     {
1201         exit (1);
1202     }
1203 }
1204
1205 /* main function */
1206 void screenhack (Display *dpy, Window win)
1207 {
1208     display = dpy;
1209     window = win;
1210
1211     initParams ();
1212     setup ();
1213
1214     /* make a valid set to erase at first */
1215     renderSegs ();
1216     
1217     for (;;) 
1218     {
1219         oneIteration ();
1220         XSync (dpy, False);
1221         screenhack_handle_events (dpy);
1222         usleep (delay);
1223     }
1224 }