http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.00.tar.gz
[xscreensaver] / hacks / nerverot.c
1 /* nerverot, nervous rotation of random thingies, v1.4
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  * generic blot setup and manipulation
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 /* randomly rotate the blots around the origin */
235 static void randomlyRotateBlots (void)
236 {
237     int n;
238
239     /* random amounts to rotate about each axis */
240     FLOAT xRot = RAND_FLOAT_PM1 * M_PI;
241     FLOAT yRot = RAND_FLOAT_PM1 * M_PI;
242     FLOAT zRot = RAND_FLOAT_PM1 * M_PI;
243
244     /* rotation factors */
245     FLOAT sinX = sin (xRot);
246     FLOAT cosX = cos (xRot);
247     FLOAT sinY = sin (yRot);
248     FLOAT cosY = cos (yRot);
249     FLOAT sinZ = sin (zRot);
250     FLOAT cosZ = cos (zRot);
251
252     for (n = 0; n < blotCount; n++)
253     {
254         FLOAT x1 = blots[n].x;
255         FLOAT y1 = blots[n].y;
256         FLOAT z1 = blots[n].z;
257         FLOAT x2, y2, z2;
258
259         /* rotate on z axis */
260         x2 = x1 * cosZ - y1 * sinZ;
261         y2 = x1 * sinZ + y1 * cosZ;
262         z2 = z1;
263
264         /* rotate on x axis */
265         y1 = y2 * cosX - z2 * sinX;
266         z1 = y2 * sinX + z2 * cosX;
267         x1 = x2;
268
269         /* rotate on y axis */
270         z2 = z1 * cosY - x1 * sinY;
271         x2 = z1 * sinY + x1 * cosY;
272         y2 = y1;
273
274         blots[n].x = x2;
275         blots[n].y = y2;
276         blots[n].z = z2;
277     }
278 }
279
280
281
282 /*
283  * blot configurations
284  */
285
286 /* set up the initial array of blots to be a at the edge of a sphere */
287 static void setupBlotsSphere (void)
288 {
289     int n;
290
291     blotCount = requestedBlotCount;
292     blots = calloc (sizeof (Blot), blotCount);
293
294     for (n = 0; n < blotCount; n++)
295     {
296         /* pick a spot, but reject if its radius is < 0.2 or > 1 to
297          * avoid scaling problems */
298         FLOAT x, y, z, radius;
299
300         for (;;)
301         {
302             x = RAND_FLOAT_PM1;
303             y = RAND_FLOAT_PM1;
304             z = RAND_FLOAT_PM1;
305
306             radius = sqrt (x * x + y * y + z * z);
307             if ((radius >= 0.2) && (radius <= 1.0))
308             {
309                 break;
310             }
311         }
312
313         x /= radius;
314         y /= radius;
315         z /= radius;
316
317         initBlot (&blots[n], x, y, z);
318     }
319 }
320
321 /* set up the initial array of blots to be a simple cube */
322 static void setupBlotsCube (void)
323 {
324     int i, j, k, n;
325
326     /* derive blotsPerEdge from blotCount, but then do the reverse
327      * since roundoff may have changed blotCount */
328     int blotsPerEdge = ((requestedBlotCount - 8) / 12) + 2;
329     FLOAT distBetween;
330
331     if (blotsPerEdge < 2)
332     {
333         blotsPerEdge = 2;
334     }
335
336     distBetween = 2.0 / (blotsPerEdge - 1.0);
337
338     blotCount = 8 + (blotsPerEdge - 2) * 12;
339     blots = calloc (sizeof (Blot), blotCount);
340     n = 0;
341
342     /* define the corners */
343     for (i = -1; i < 2; i += 2)
344     {
345         for (j = -1; j < 2; j += 2)
346         {
347             for (k = -1; k < 2; k += 2)
348             {
349                 initBlot (&blots[n], i, j, k);
350                 n++;
351             } 
352         }
353     }
354
355     /* define the edges */
356     for (i = 1; i < (blotsPerEdge - 1); i++)
357     {
358         FLOAT varEdge = distBetween * i - 1;
359         initBlot (&blots[n++], varEdge, -1, -1);
360         initBlot (&blots[n++], varEdge,  1, -1);
361         initBlot (&blots[n++], varEdge, -1,  1);
362         initBlot (&blots[n++], varEdge,  1,  1);
363         initBlot (&blots[n++], -1, varEdge, -1);
364         initBlot (&blots[n++],  1, varEdge, -1);
365         initBlot (&blots[n++], -1, varEdge,  1);
366         initBlot (&blots[n++],  1, varEdge,  1);
367         initBlot (&blots[n++], -1, -1, varEdge);
368         initBlot (&blots[n++],  1, -1, varEdge);
369         initBlot (&blots[n++], -1,  1, varEdge);
370         initBlot (&blots[n++],  1,  1, varEdge);
371     }
372
373     scaleBlotsToRadius1 ();
374     randomlyReorderBlots ();
375     randomlyRotateBlots ();
376 }
377
378 /* set up the initial array of blots to be a cylinder */
379 static void setupBlotsCylinder (void)
380 {
381     int i, j, n;
382     FLOAT distBetween;
383
384     /* derive blotsPerEdge and blotsPerRing from blotCount, but then do the
385      * reverse since roundoff may have changed blotCount */
386     FLOAT reqRoot = sqrt ((FLOAT) requestedBlotCount);
387     int blotsPerRing = ceil (RAND_FLOAT_PM1 * reqRoot) / 2 + reqRoot;
388     int blotsPerEdge = requestedBlotCount / blotsPerRing;
389
390     if (blotsPerRing < 2)
391     {
392         blotsPerRing = 2;
393     }
394
395     if (blotsPerEdge < 2)
396     {
397         blotsPerEdge = 2;
398     }
399
400     distBetween = 2.0 / (blotsPerEdge - 1);
401
402     blotCount = blotsPerEdge * blotsPerRing;
403     blots = calloc (sizeof (Blot), blotCount);
404     n = 0;
405
406     /* define the edges */
407     for (i = 0; i < blotsPerRing; i++)
408     {
409         FLOAT x = sin (2 * M_PI / blotsPerRing * i);
410         FLOAT y = cos (2 * M_PI / blotsPerRing * i);
411         for (j = 0; j < blotsPerEdge; j++)
412         {
413             initBlot (&blots[n], x, y, j * distBetween - 1);
414             n++;
415         }
416     }
417
418     scaleBlotsToRadius1 ();
419     randomlyReorderBlots ();
420     randomlyRotateBlots ();
421 }
422
423 /* set up the initial array of blots to be a squiggle */
424 static void setupBlotsSquiggle (void)
425 {
426     FLOAT x, y, z, xv, yv, zv, len;
427     int minCoor, maxCoor;
428     int n;
429
430     blotCount = requestedBlotCount;
431     blots = calloc (sizeof (Blot), blotCount);
432
433     maxCoor = (int) (RAND_FLOAT_01 * 5) + 1;
434     minCoor = -maxCoor;
435
436     x = RAND_FLOAT_PM1;
437     y = RAND_FLOAT_PM1;
438     z = RAND_FLOAT_PM1;
439
440     xv = RAND_FLOAT_PM1;
441     yv = RAND_FLOAT_PM1;
442     zv = RAND_FLOAT_PM1;
443     len = sqrt (xv * xv + yv * yv + zv * zv);
444     xv /= len;
445     yv /= len;
446     zv /= len;
447     
448     for (n = 0; n < blotCount; n++)
449     {
450         FLOAT newx, newy, newz;
451         initBlot (&blots[n], x, y, z);
452
453         for (;;)
454         {
455             xv += RAND_FLOAT_PM1 * 0.1;
456             yv += RAND_FLOAT_PM1 * 0.1;
457             zv += RAND_FLOAT_PM1 * 0.1;
458             len = sqrt (xv * xv + yv * yv + zv * zv);
459             xv /= len;
460             yv /= len;
461             zv /= len;
462
463             newx = x + xv * 0.1;
464             newy = y + yv * 0.1;
465             newz = z + zv * 0.1;
466
467             if (   (newx >= minCoor) && (newx <= maxCoor)
468                 && (newy >= minCoor) && (newy <= maxCoor)
469                 && (newz >= minCoor) && (newz <= maxCoor))
470             {
471                 break;
472             }
473         }
474
475         x = newx;
476         y = newy;
477         z = newz;
478     }
479
480     scaleBlotsToRadius1 ();
481     randomlyReorderBlots ();
482 }
483
484 /* set up the initial array of blots to be near the corners of a
485  * cube, distributed slightly */
486 static void setupBlotsCubeCorners (void)
487 {
488     int n;
489
490     blotCount = requestedBlotCount;
491     blots = calloc (sizeof (Blot), blotCount);
492
493     for (n = 0; n < blotCount; n++)
494     {
495         FLOAT x = rint (RAND_FLOAT_01) * 2 - 1;
496         FLOAT y = rint (RAND_FLOAT_01) * 2 - 1;
497         FLOAT z = rint (RAND_FLOAT_01) * 2 - 1;
498
499         x += RAND_FLOAT_PM1 * 0.3;
500         y += RAND_FLOAT_PM1 * 0.3;
501         z += RAND_FLOAT_PM1 * 0.3;
502
503         initBlot (&blots[n], x, y, z);
504     }
505
506     scaleBlotsToRadius1 ();
507     randomlyRotateBlots ();
508 }
509
510 /* set up the initial array of blots to be randomly distributed
511  * on the surface of a tetrahedron */
512 static void setupBlotsTetrahedron (void)
513 {
514     /* table of corners of the tetrahedron */
515     static FLOAT cor[4][3] = { {  0.0,   1.0,  0.0 },
516                                { -0.75, -0.5, -0.433013 },
517                                {  0.0,  -0.5,  0.866025 },
518                                {  0.75, -0.5, -0.433013 } };
519
520     int n, c;
521
522     /* derive blotsPerSurface from blotCount, but then do the reverse
523      * since roundoff may have changed blotCount */
524     int blotsPerSurface = requestedBlotCount / 4;
525
526     blotCount = blotsPerSurface * 4;
527     blots = calloc (sizeof (Blot), blotCount);
528
529     for (n = 0; n < blotCount; n += 4)
530     {
531         /* pick a random point on a unit right triangle */
532         FLOAT rawx = RAND_FLOAT_01;
533         FLOAT rawy = RAND_FLOAT_01;
534
535         if ((rawx + rawy) > 1)
536         {
537             /* swap coords into place */
538             FLOAT t = 1.0 - rawx;
539             rawx = 1.0 - rawy;
540             rawy = t;
541         }
542
543         /* translate the point to be on each of the surfaces */
544         for (c = 0; c < 4; c++)
545         {
546             FLOAT x, y, z;
547             
548             int c1 = (c + 1) % 4;
549             int c2 = (c + 2) % 4;
550             
551             x = (cor[c1][0] - cor[c][0]) * rawx + 
552                 (cor[c2][0] - cor[c][0]) * rawy + 
553                 cor[c][0];
554
555             y = (cor[c1][1] - cor[c][1]) * rawx + 
556                 (cor[c2][1] - cor[c][1]) * rawy + 
557                 cor[c][1];
558
559             z = (cor[c1][2] - cor[c][2]) * rawx + 
560                 (cor[c2][2] - cor[c][2]) * rawy + 
561                 cor[c][2];
562
563             initBlot (&blots[n + c], x, y, z);
564         }
565     }
566
567     randomlyRotateBlots ();
568 }
569
570 /* set up the initial array of blots to be an almost-evenly-distributed
571  * square sheet */
572 static void setupBlotsSheet (void)
573 {
574     int x, y;
575
576     int blotsPerDimension = floor (sqrt (requestedBlotCount));
577     FLOAT spaceBetween;
578
579     if (blotsPerDimension < 2)
580     {
581         blotsPerDimension = 2;
582     }
583
584     spaceBetween = 2.0 / (blotsPerDimension - 1);
585
586     blotCount = blotsPerDimension * blotsPerDimension;
587     blots = calloc (sizeof (Blot), blotCount);
588
589     for (x = 0; x < blotsPerDimension; x++)
590     {
591         for (y = 0; y < blotsPerDimension; y++)
592         {
593             FLOAT x1 = x * spaceBetween - 1.0;
594             FLOAT y1 = y * spaceBetween - 1.0;
595             FLOAT z1 = 0.0;
596
597             x1 += RAND_FLOAT_PM1 * spaceBetween / 3;
598             y1 += RAND_FLOAT_PM1 * spaceBetween / 3;
599             z1 += RAND_FLOAT_PM1 * spaceBetween / 2;
600
601             initBlot (&blots[x + y * blotsPerDimension], x1, y1, z1);
602         }
603     }
604
605     scaleBlotsToRadius1 ();
606     randomlyReorderBlots ();
607     randomlyRotateBlots ();
608 }
609
610 /* set up the initial array of blots to be a swirlycone */
611 static void setupBlotsSwirlyCone (void)
612 {
613     FLOAT radSpace = 1.0 / (requestedBlotCount - 1);
614     FLOAT zSpace = radSpace * 2;
615     FLOAT rotAmt = RAND_FLOAT_PM1 * M_PI / 10;
616
617     int n;
618     FLOAT rot = 0.0;
619
620     blotCount = requestedBlotCount;
621     blots = calloc (sizeof (Blot), blotCount);
622
623     for (n = 0; n < blotCount; n++)
624     {
625         FLOAT radius = n * radSpace;
626         FLOAT x = cos (rot) * radius;
627         FLOAT y = sin (rot) * radius;
628         FLOAT z = n * zSpace - 1.0;
629
630         rot += rotAmt;
631         initBlot (&blots[n], x, y, z);
632     }
633
634     scaleBlotsToRadius1 ();
635     randomlyReorderBlots ();
636     randomlyRotateBlots ();
637 }
638
639 /* forward declaration for recursive use immediately below */
640 static void setupBlots (void);
641
642 /* set up the blots to be two of the other choices, placed next to
643  * each other */
644 static void setupBlotsDuo (void)
645 {
646     int origRequest = requestedBlotCount;
647     FLOAT tx, ty, tz, radius;
648     Blot *blots1, *blots2;
649     int count1, count2;
650     int n;
651
652     if (requestedBlotCount < 15)
653     {
654         /* special case bottom-out */
655         setupBlotsSphere ();
656         return;
657     }
658
659     tx = RAND_FLOAT_PM1;
660     ty = RAND_FLOAT_PM1;
661     tz = RAND_FLOAT_PM1;
662     radius = sqrt (tx * tx + ty * ty + tz * tz);
663     tx /= radius;
664     ty /= radius;
665     tz /= radius;
666
667     /* recursive call to setup set 1 */
668     requestedBlotCount = origRequest / 2;
669     setupBlots ();
670
671     if (blotCount >= origRequest)
672     {
673         /* return immediately if this satisfies the original count request */
674         requestedBlotCount = origRequest;
675         return;
676     }
677
678     blots1 = blots;
679     count1 = blotCount;
680     blots = NULL;
681     blotCount = 0;
682     
683     /* translate to new position */
684     for (n = 0; n < count1; n++)
685     {
686         blots1[n].x += tx;
687         blots1[n].y += ty;
688         blots1[n].z += tz;
689     }
690
691     /* recursive call to setup set 2 */
692     requestedBlotCount = origRequest - count1;
693     setupBlots ();
694     blots2 = blots;
695     count2 = blotCount;
696
697     /* translate to new position */
698     for (n = 0; n < count2; n++)
699     {
700         blots2[n].x -= tx;
701         blots2[n].y -= ty;
702         blots2[n].z -= tz;
703     }
704
705     /* combine the two arrays */
706     blotCount = count1 + count2;
707     blots = calloc (sizeof (Blot), blotCount);
708     memcpy (&blots[0],      blots1, sizeof (Blot) * count1);
709     memcpy (&blots[count1], blots2, sizeof (Blot) * count2);
710     free (blots1);
711     free (blots2);
712
713     scaleBlotsToRadius1 ();
714     randomlyReorderBlots ();
715
716     /* restore the original requested count, for future iterations */
717     requestedBlotCount = origRequest;
718 }
719
720
721
722 /*
723  * main blot setup
724  */
725
726 /* free the blots, in preparation for a new shape */
727 static void freeBlots (void)
728 {
729     if (blots != NULL)
730     {
731         free (blots);
732         blots = NULL;
733     }
734
735     if (segsToErase != NULL)
736     {
737         free (segsToErase);
738         segsToErase = NULL;
739     }
740
741     if (segsToDraw != NULL)
742     {
743         free (segsToDraw);
744         segsToDraw = NULL;
745     }
746 }
747
748 /* set up the initial arrays of blots */
749 static void setupBlots (void)
750 {
751     int which = RAND_FLOAT_01 * 11;
752
753     freeBlots ();
754
755     switch (which)
756     {
757         case 0:
758             setupBlotsCube ();
759             break;
760         case 1:
761             setupBlotsSphere ();
762             break;
763         case 2:
764             setupBlotsCylinder ();
765             break;
766         case 3:
767             setupBlotsSquiggle ();
768             break;
769         case 4:
770             setupBlotsCubeCorners ();
771             break;
772         case 5:
773             setupBlotsTetrahedron ();
774             break;
775         case 6:
776             setupBlotsSheet ();
777             break;
778         case 7:
779             setupBlotsSwirlyCone ();
780             break;
781         case 8:
782         case 9:
783         case 10:
784             setupBlotsDuo ();
785             break;
786     }
787 }
788
789 /* set up the segments arrays */
790 static void setupSegs (void)
791 {
792     /* there are blotShapeCount - 1 line segments per blot */
793     segCount = blotCount * (blotShapeCount - 1);
794     segsToErase = calloc (sizeof (LineSegment), segCount);
795     segsToDraw = calloc (sizeof (LineSegment), segCount);
796
797     /* erase the world */
798     XFillRectangle (display, drawable, gcs[0], 0, 0, 
799                     windowWidth, windowHeight);
800 }
801
802
803
804 /*
805  * color setup stuff
806  */
807
808 /* set up the colormap */
809 static void setupColormap (XWindowAttributes *xgwa)
810 {
811     int n;
812     XGCValues gcv;
813     XColor *colors = (XColor *) calloc (sizeof (XColor), colorCount + 1);
814
815     unsigned short r, g, b;
816     int h1, h2;
817     double s1, s2, v1, v2;
818
819     r = RAND_FLOAT_01 * 0x10000;
820     g = RAND_FLOAT_01 * 0x10000;
821     b = RAND_FLOAT_01 * 0x10000;
822     rgb_to_hsv (r, g, b, &h1, &s1, &v1);
823     v1 = 1.0;
824     s1 = 1.0;
825
826     r = RAND_FLOAT_01 * 0x10000;
827     g = RAND_FLOAT_01 * 0x10000;
828     b = RAND_FLOAT_01 * 0x10000;
829     rgb_to_hsv (r, g, b, &h2, &s2, &v2);
830     s2 = 0.7;
831     v2 = 0.7;
832     
833     colors[0].pixel = get_pixel_resource ("background", "Background",
834                                           display, xgwa->colormap);
835     
836     make_color_ramp (display, xgwa->colormap, h1, s1, v1, h2, s2, v2,
837                      colors + 1, &colorCount, False, True, False);
838
839     if (colorCount < 1)
840     {
841         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
842         exit (-1);
843     }
844     
845     gcs = (GC *) calloc (sizeof (GC), colorCount + 1);
846
847     for (n = 0; n <= colorCount; n++) 
848     {
849         gcv.foreground = colors[n].pixel;
850         gcv.line_width = lineWidth;
851         gcs[n] = XCreateGC (display, window, GCForeground | GCLineWidth, &gcv);
852     }
853
854     free (colors);
855 }
856
857
858
859 /*
860  * overall setup stuff
861  */
862
863 /* set up the system */
864 static void setup (void)
865 {
866     XWindowAttributes xgwa;
867
868     XGetWindowAttributes (display, window, &xgwa);
869
870     windowWidth = xgwa.width;
871     windowHeight = xgwa.height;
872     centerX = windowWidth / 2;
873     centerY = windowHeight / 2;
874     baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
875
876     if (doubleBuffer)
877     {
878         drawable = XCreatePixmap (display, window, xgwa.width, xgwa.height,
879                                   xgwa.depth);
880     }
881     else
882     {
883         drawable = window;
884     }
885
886     setupColormap (&xgwa);
887     setupBlots ();
888     setupSegs ();
889
890     /* set up the initial rotation, scale, and light values as random, but
891      * with the targets equal to where it is */
892     xRot = xRotTarget = RAND_FLOAT_01 * M_PI;
893     yRot = yRotTarget = RAND_FLOAT_01 * M_PI;
894     zRot = zRotTarget = RAND_FLOAT_01 * M_PI;
895     curScale = scaleTarget = RAND_FLOAT_01 * (maxScale - minScale) + minScale;
896     lightX = lightXTarget = RAND_FLOAT_PM1;
897     lightY = lightYTarget = RAND_FLOAT_PM1;
898     lightZ = lightZTarget = RAND_FLOAT_PM1;
899
900     itersTillNext = RAND_FLOAT_01 * maxIters;
901 }
902
903
904
905 /*
906  * the simulation
907  */
908
909 /* "render" the blots into segsToDraw, with the current rotation factors */
910 static void renderSegs (void)
911 {
912     int n;
913     int m = 0;
914
915     /* rotation factors */
916     FLOAT sinX = sin (xRot);
917     FLOAT cosX = cos (xRot);
918     FLOAT sinY = sin (yRot);
919     FLOAT cosY = cos (yRot);
920     FLOAT sinZ = sin (zRot);
921     FLOAT cosZ = cos (zRot);
922
923     for (n = 0; n < blotCount; n++)
924     {
925         Blot *b = &blots[n];
926         int i, j;
927         int baseX, baseY;
928         FLOAT radius;
929         int x[3][3];
930         int y[3][3];
931         int color;
932
933         FLOAT x1 = blots[n].x;
934         FLOAT y1 = blots[n].y;
935         FLOAT z1 = blots[n].z;
936         FLOAT x2, y2, z2;
937
938         /* rotate on z axis */
939         x2 = x1 * cosZ - y1 * sinZ;
940         y2 = x1 * sinZ + y1 * cosZ;
941         z2 = z1;
942
943         /* rotate on x axis */
944         y1 = y2 * cosX - z2 * sinX;
945         z1 = y2 * sinX + z2 * cosX;
946         x1 = x2;
947
948         /* rotate on y axis */
949         z2 = z1 * cosY - x1 * sinY;
950         x2 = z1 * sinY + x1 * cosY;
951         y2 = y1;
952
953         /* the color to draw is based on the distance from the light of
954          * the post-rotation blot */
955         x1 = x2 - lightX;
956         y1 = y2 - lightY;
957         z1 = z2 - lightZ;
958         color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * colorCount;
959         if (color > colorCount)
960         {
961             color = colorCount;
962         }
963
964         /* set up the base screen coordinates for drawing */
965         baseX = x2 / 2 * baseScale * curScale + centerX + centerXOff;
966         baseY = y2 / 2 * baseScale * curScale + centerY + centerYOff;
967         
968         radius = (z2 + 1) / 2 * (maxRadius - minRadius) + minRadius;
969
970         for (i = 0; i < 3; i++)
971         {
972             for (j = 0; j < 3; j++)
973             {
974                 x[i][j] = baseX + 
975                     ((i - 1) + (b->xoff[i][j] * maxNerveRadius)) * radius;
976                 y[i][j] = baseY + 
977                     ((j - 1) + (b->yoff[i][j] * maxNerveRadius)) * radius;
978             }
979         }
980
981         for (i = 1; i < blotShapeCount; i++)
982         {
983             segsToDraw[m].gc = gcs[color];
984             segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
985             segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
986             segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
987             segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
988             m++;
989         }
990     }
991 }
992
993 /* update blots, adjusting the offsets and rotation factors. */
994 static void updateWithFeeling (void)
995 {
996     int n, i, j;
997
998     /* pick a new model if the time is right */
999     itersTillNext--;
1000     if (itersTillNext < 0)
1001     {
1002         itersTillNext = RAND_FLOAT_01 * maxIters;
1003         setupBlots ();
1004         setupSegs ();
1005         renderSegs ();
1006     }
1007
1008     /* update the rotation factors by moving them a bit toward the targets */
1009     xRot = xRot + (xRotTarget - xRot) * iterAmt; 
1010     yRot = yRot + (yRotTarget - yRot) * iterAmt;
1011     zRot = zRot + (zRotTarget - zRot) * iterAmt;
1012
1013     /* similarly the scale factor */
1014     curScale = curScale + (scaleTarget - curScale) * iterAmt;
1015
1016     /* and similarly the light position */
1017     lightX = lightX + (lightXTarget - lightX) * iterAmt; 
1018     lightY = lightY + (lightYTarget - lightY) * iterAmt; 
1019     lightZ = lightZ + (lightZTarget - lightZ) * iterAmt; 
1020
1021     /* for each blot... */
1022     for (n = 0; n < blotCount; n++)
1023     {
1024         /* add a bit of random jitter to xoff/yoff */
1025         for (i = 0; i < 3; i++)
1026         {
1027             for (j = 0; j < 3; j++)
1028             {
1029                 FLOAT newOff;
1030
1031                 newOff = blots[n].xoff[i][j] + RAND_FLOAT_PM1 * nervousness;
1032                 if (newOff < -1) newOff = -(newOff + 1) - 1;
1033                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
1034                 blots[n].xoff[i][j] = newOff;
1035
1036                 newOff = blots[n].yoff[i][j] + RAND_FLOAT_PM1 * nervousness;
1037                 if (newOff < -1) newOff = -(newOff + 1) - 1;
1038                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
1039                 blots[n].yoff[i][j] = newOff;
1040             }
1041         }
1042     }
1043
1044     /* depending on random chance, update one or more factors */
1045     if (RAND_FLOAT_01 <= eventChance)
1046     {
1047         int which = RAND_FLOAT_01 * 14;
1048         switch (which)
1049         {
1050             case 0:
1051             {
1052                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1053                 break;
1054             }
1055             case 1:
1056             {
1057                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1058                 break;
1059             }
1060             case 2:
1061             {
1062                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1063                 break;
1064             }
1065             case 3:
1066             {
1067                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1068                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1069                 break;
1070             }
1071             case 4:
1072             {
1073                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1074                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1075                 break;
1076             }
1077             case 5:
1078             {
1079                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1080                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1081                 break;
1082             }
1083             case 6:
1084             {
1085                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1086                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1087                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1088                 break;
1089             }
1090             case 7:
1091             {
1092                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
1093                 break;
1094             }
1095             case 8:
1096             {
1097                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
1098                 break;
1099             }
1100             case 9:
1101             {
1102                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
1103                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
1104                 break;
1105             }
1106             case 10:
1107             {
1108                 scaleTarget = 
1109                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
1110                 break;
1111             }
1112             case 11:
1113             {
1114                 curScale = 
1115                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
1116                 break;
1117             }
1118             case 12:
1119             {
1120                 lightX = RAND_FLOAT_PM1;
1121                 lightY = RAND_FLOAT_PM1;
1122                 lightZ = RAND_FLOAT_PM1;
1123                 break;
1124             }
1125             case 13:
1126             {
1127                 lightXTarget = RAND_FLOAT_PM1;
1128                 lightYTarget = RAND_FLOAT_PM1;
1129                 lightZTarget = RAND_FLOAT_PM1;
1130                 break;
1131             }
1132         }
1133     }
1134 }
1135
1136 /* erase segsToErase and draw segsToDraw */
1137 static void eraseAndDraw (void)
1138 {
1139     int n;
1140
1141     for (n = 0; n < segCount; n++)
1142     {
1143         LineSegment *seg = &segsToErase[n];
1144         XDrawLine (display, drawable, gcs[0], 
1145                    seg->x1, seg->y1, seg->x2, seg->y2);
1146         seg = &segsToDraw[n];
1147         XDrawLine (display, drawable, seg->gc,
1148                    seg->x1, seg->y1, seg->x2, seg->y2);
1149     }
1150
1151     if (doubleBuffer)
1152     {
1153         XCopyArea (display, drawable, window, gcs[0], 0, 0, 
1154                    windowWidth, windowHeight, 0, 0);
1155     }
1156 }
1157
1158 /* do one iteration */
1159 static void oneIteration (void)
1160 {
1161     /* switch segsToErase and segsToDraw */
1162     LineSegment *temp = segsToDraw;
1163     segsToDraw = segsToErase;
1164     segsToErase = temp;
1165
1166     /* update the model */
1167     updateWithFeeling ();
1168
1169     /* render new segments */
1170     renderSegs ();
1171
1172     /* erase old segments and draw new ones */
1173     eraseAndDraw ();
1174 }
1175
1176 char *progclass = "NerveRot";
1177
1178 char *defaults [] = {
1179     ".background:       black",
1180     ".foreground:       white",
1181     "*count:            250",
1182     "*colors:           4",
1183     "*delay:            10000",
1184     "*maxIters:         1200",
1185     "*doubleBuffer:     false",
1186     "*eventChance:      0.2",
1187     "*iterAmt:          0.01",
1188     "*lineWidth:        0",
1189     "*minScale:         0.6",
1190     "*maxScale:         1.75",
1191     "*minRadius:        3",
1192     "*maxRadius:        25",
1193     "*maxNerveRadius:   0.7",
1194     "*nervousness:      0.3",
1195     0
1196 };
1197
1198 XrmOptionDescRec options [] = {
1199   { "-count",            ".count",          XrmoptionSepArg, 0 },
1200   { "-colors",           ".colors",         XrmoptionSepArg, 0 },
1201   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
1202   { "-max-iters",        ".maxIters",       XrmoptionSepArg, 0 },
1203   { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
1204   { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
1205   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
1206   { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
1207   { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
1208   { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
1209   { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
1210   { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
1211   { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
1212   { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
1213   { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
1214   { 0, 0, 0, 0 }
1215 };
1216
1217 /* initialize the user-specifiable params */
1218 static void initParams (void)
1219 {
1220     int problems = 0;
1221
1222     delay = get_integer_resource ("delay", "Delay");
1223     if (delay < 0)
1224     {
1225         fprintf (stderr, "error: delay must be at least 0\n");
1226         problems = 1;
1227     }
1228     
1229     maxIters = get_integer_resource ("maxIters", "Integer");
1230     if (maxIters < 0)
1231     {
1232         fprintf (stderr, "error: maxIters must be at least 0\n");
1233         problems = 1;
1234     }
1235     
1236     doubleBuffer = get_boolean_resource ("doubleBuffer", "Boolean");
1237
1238     requestedBlotCount = get_integer_resource ("count", "Count");
1239     if (requestedBlotCount <= 0)
1240     {
1241         fprintf (stderr, "error: count must be at least 0\n");
1242         problems = 1;
1243     }
1244
1245     colorCount = get_integer_resource ("colors", "Colors");
1246     if (colorCount <= 0)
1247     {
1248         fprintf (stderr, "error: colors must be at least 1\n");
1249         problems = 1;
1250     }
1251
1252     lineWidth = get_integer_resource ("lineWidth", "LineWidth");
1253     if (lineWidth < 0)
1254     {
1255         fprintf (stderr, "error: line width must be at least 0\n");
1256         problems = 1;
1257     }
1258
1259     nervousness = get_float_resource ("nervousness", "Float");
1260     if ((nervousness < 0) || (nervousness > 1))
1261     {
1262         fprintf (stderr, "error: nervousness must be in the range 0..1\n");
1263         problems = 1;
1264     }
1265
1266     maxNerveRadius = get_float_resource ("maxNerveRadius", "Float");
1267     if ((maxNerveRadius < 0) || (maxNerveRadius > 1))
1268     {
1269         fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
1270         problems = 1;
1271     }
1272
1273     eventChance = get_float_resource ("eventChance", "Float");
1274     if ((eventChance < 0) || (eventChance > 1))
1275     {
1276         fprintf (stderr, "error: eventChance must be in the range 0..1\n");
1277         problems = 1;
1278     }
1279
1280     iterAmt = get_float_resource ("iterAmt", "Float");
1281     if ((iterAmt < 0) || (iterAmt > 1))
1282     {
1283         fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
1284         problems = 1;
1285     }
1286
1287     minScale = get_float_resource ("minScale", "Float");
1288     if ((minScale < 0) || (minScale > 10))
1289     {
1290         fprintf (stderr, "error: minScale must be in the range 0..10\n");
1291         problems = 1;
1292     }
1293
1294     maxScale = get_float_resource ("maxScale", "Float");
1295     if ((maxScale < 0) || (maxScale > 10))
1296     {
1297         fprintf (stderr, "error: maxScale must be in the range 0..10\n");
1298         problems = 1;
1299     }
1300
1301     if (maxScale < minScale)
1302     {
1303         fprintf (stderr, "error: maxScale must be >= minScale\n");
1304         problems = 1;
1305     }   
1306
1307     minRadius = get_integer_resource ("minRadius", "Integer");
1308     if ((minRadius < 1) || (minRadius > 100))
1309     {
1310         fprintf (stderr, "error: minRadius must be in the range 1..100\n");
1311         problems = 1;
1312     }
1313
1314     maxRadius = get_integer_resource ("maxRadius", "Integer");
1315     if ((maxRadius < 1) || (maxRadius > 100))
1316     {
1317         fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
1318         problems = 1;
1319     }
1320
1321     if (maxRadius < minRadius)
1322     {
1323         fprintf (stderr, "error: maxRadius must be >= minRadius\n");
1324         problems = 1;
1325     }   
1326
1327     if (problems)
1328     {
1329         exit (1);
1330     }
1331 }
1332
1333 /* main function */
1334 void screenhack (Display *dpy, Window win)
1335 {
1336     display = dpy;
1337     window = win;
1338
1339     initParams ();
1340     setup ();
1341
1342     /* make a valid set to erase at first */
1343     renderSegs ();
1344     
1345     for (;;) 
1346     {
1347         oneIteration ();
1348         XSync (dpy, False);
1349         screenhack_handle_events (dpy);
1350         usleep (delay);
1351     }
1352 }