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