39f33479185112a346c428127d283773cede1d8c
[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         requestedBlotCount = origRequest;
487         return;
488     }
489
490     blots1 = blots;
491     count1 = blotCount;
492     blots = NULL;
493     blotCount = 0;
494     
495     /* translate to new position */
496     for (n = 0; n < count1; n++)
497     {
498         blots1[n].x += tx;
499         blots1[n].y += ty;
500         blots1[n].z += tz;
501     }
502
503     /* recursive call to setup set 2 */
504     requestedBlotCount = origRequest - count1;
505     setupBlots ();
506     blots2 = blots;
507     count2 = blotCount;
508
509     /* translate to new position */
510     for (n = 0; n < count2; n++)
511     {
512         blots2[n].x -= tx;
513         blots2[n].y -= ty;
514         blots2[n].z -= tz;
515     }
516
517     /* combine the two arrays */
518     blotCount = count1 + count2;
519     blots = calloc (sizeof (Blot), blotCount);
520     memcpy (&blots[0],      blots1, sizeof (Blot) * count1);
521     memcpy (&blots[count1], blots2, sizeof (Blot) * count2);
522     free (blots1);
523     free (blots2);
524
525     scaleBlotsToRadius1 ();
526     randomlyReorderBlots ();
527
528     /* restore the original requested count, for future iterations */
529     requestedBlotCount = origRequest;
530 }
531
532
533
534 /* free the blots, in preparation for a new shape */
535 static void freeBlots (void)
536 {
537     if (blots != NULL)
538     {
539         free (blots);
540         blots = NULL;
541     }
542
543     if (segsToErase != NULL)
544     {
545         free (segsToErase);
546         segsToErase = NULL;
547     }
548
549     if (segsToDraw != NULL)
550     {
551         free (segsToDraw);
552         segsToDraw = NULL;
553     }
554 }
555
556
557
558 /* set up the initial arrays of blots */
559 static void setupBlots (void)
560 {
561     int which = RAND_FLOAT_01 * 7;
562
563     freeBlots ();
564
565     switch (which)
566     {
567         case 0:
568             setupBlotsCube ();
569             break;
570         case 1:
571             setupBlotsSphere ();
572             break;
573         case 2:
574             setupBlotsCylinder ();
575             break;
576         case 3:
577             setupBlotsSquiggle ();
578             break;
579         case 4:
580             setupBlotsCubeCorners ();
581             break;
582         case 5:
583         case 6:
584             setupBlotsDuo ();
585             break;
586     }
587 }
588
589
590
591 /* set up the segments arrays */
592 static void setupSegs (void)
593 {
594     /* there are blotShapeCount - 1 line segments per blot */
595     segCount = blotCount * (blotShapeCount - 1);
596     segsToErase = calloc (sizeof (LineSegment), segCount);
597     segsToDraw = calloc (sizeof (LineSegment), segCount);
598
599     /* erase the world */
600     XFillRectangle (display, drawable, gcs[0], 0, 0, 
601                     windowWidth, windowHeight);
602 }
603
604
605
606 /*
607  * color setup stuff
608  */
609
610 /* set up the colormap */
611 static void setupColormap (XWindowAttributes *xgwa)
612 {
613     int n;
614     XGCValues gcv;
615     XColor *colors = (XColor *) calloc (sizeof (XColor), colorCount + 1);
616
617     unsigned short r, g, b;
618     int h1, h2;
619     double s1, s2, v1, v2;
620
621     r = RAND_FLOAT_01 * 0x10000;
622     g = RAND_FLOAT_01 * 0x10000;
623     b = RAND_FLOAT_01 * 0x10000;
624     rgb_to_hsv (r, g, b, &h1, &s1, &v1);
625     v1 = 1.0;
626     s1 = 1.0;
627
628     r = RAND_FLOAT_01 * 0x10000;
629     g = RAND_FLOAT_01 * 0x10000;
630     b = RAND_FLOAT_01 * 0x10000;
631     rgb_to_hsv (r, g, b, &h2, &s2, &v2);
632     s2 = 0.7;
633     v2 = 0.7;
634     
635     colors[0].pixel = get_pixel_resource ("background", "Background",
636                                           display, xgwa->colormap);
637     
638     make_color_ramp (display, xgwa->colormap, h1, s1, v1, h2, s2, v2,
639                      colors + 1, &colorCount, False, True, False);
640
641     if (colorCount < 1)
642     {
643         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
644         exit (-1);
645     }
646     
647     gcs = (GC *) calloc (sizeof (GC), colorCount + 1);
648
649     for (n = 0; n <= colorCount; n++) 
650     {
651         gcv.foreground = colors[n].pixel;
652         gcv.line_width = lineWidth;
653         gcs[n] = XCreateGC (display, window, GCForeground | GCLineWidth, &gcv);
654     }
655
656     free (colors);
657 }
658
659
660
661 /*
662  * overall setup stuff
663  */
664
665 /* set up the system */
666 static void setup (void)
667 {
668     XWindowAttributes xgwa;
669
670     XGetWindowAttributes (display, window, &xgwa);
671
672     windowWidth = xgwa.width;
673     windowHeight = xgwa.height;
674     centerX = windowWidth / 2;
675     centerY = windowHeight / 2;
676     baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
677
678     if (doubleBuffer)
679     {
680         drawable = XCreatePixmap (display, window, xgwa.width, xgwa.height,
681                                   xgwa.depth);
682     }
683     else
684     {
685         drawable = window;
686     }
687
688     setupColormap (&xgwa);
689     setupBlots ();
690     setupSegs ();
691
692     /* set up the initial rotation, scale, and light values as random, but
693      * with the targets equal to where it is */
694     xRot = xRotTarget = RAND_FLOAT_01 * M_PI;
695     yRot = yRotTarget = RAND_FLOAT_01 * M_PI;
696     zRot = zRotTarget = RAND_FLOAT_01 * M_PI;
697     curScale = scaleTarget = RAND_FLOAT_01 * (maxScale - minScale) + minScale;
698     lightX = lightXTarget = RAND_FLOAT_PM1;
699     lightY = lightYTarget = RAND_FLOAT_PM1;
700     lightZ = lightZTarget = RAND_FLOAT_PM1;
701
702     itersTillNext = RAND_FLOAT_01 * 1234;
703 }
704
705
706
707 /*
708  * the simulation
709  */
710
711 /* "render" the blots into segsToDraw, with the current rotation factors */
712 static void renderSegs (void)
713 {
714     int n;
715     int m = 0;
716
717     /* rotation factors */
718     FLOAT sinX = sin (xRot);
719     FLOAT cosX = cos (xRot);
720     FLOAT sinY = sin (yRot);
721     FLOAT cosY = cos (yRot);
722     FLOAT sinZ = sin (zRot);
723     FLOAT cosZ = cos (zRot);
724
725     for (n = 0; n < blotCount; n++)
726     {
727         Blot *b = &blots[n];
728         int i, j;
729         int baseX, baseY;
730         FLOAT radius;
731         int x[3][3];
732         int y[3][3];
733         int color;
734
735         FLOAT x1 = blots[n].x;
736         FLOAT y1 = blots[n].y;
737         FLOAT z1 = blots[n].z;
738         FLOAT x2, y2, z2;
739
740         /* rotate on z axis */
741         x2 = x1 * cosZ - y1 * sinZ;
742         y2 = x1 * sinZ + y1 * cosZ;
743         z2 = z1;
744
745         /* rotate on x axis */
746         y1 = y2 * cosX - z2 * sinX;
747         z1 = y2 * sinX + z2 * cosX;
748         x1 = x2;
749
750         /* rotate on y axis */
751         z2 = z1 * cosY - x1 * sinY;
752         x2 = z1 * sinY + x1 * cosY;
753         y2 = y1;
754
755         /* the color to draw is based on the distance from the light of
756          * the post-rotation blot */
757         x1 = x2 - lightX;
758         y1 = y2 - lightY;
759         z1 = z2 - lightZ;
760         color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * colorCount;
761         if (color > colorCount)
762         {
763             color = colorCount;
764         }
765
766         /* set up the base screen coordinates for drawing */
767         baseX = x2 / 2 * baseScale * curScale + centerX + centerXOff;
768         baseY = y2 / 2 * baseScale * curScale + centerY + centerYOff;
769         
770         radius = (z2 + 1) / 2 * (maxRadius - minRadius) + minRadius;
771
772         for (i = 0; i < 3; i++)
773         {
774             for (j = 0; j < 3; j++)
775             {
776                 x[i][j] = baseX + 
777                     ((i - 1) + (b->xoff[i][j] * maxNerveRadius)) * radius;
778                 y[i][j] = baseY + 
779                     ((j - 1) + (b->yoff[i][j] * maxNerveRadius)) * radius;
780             }
781         }
782
783         for (i = 1; i < blotShapeCount; i++)
784         {
785             segsToDraw[m].gc = gcs[color];
786             segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
787             segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
788             segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
789             segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
790             m++;
791         }
792     }
793 }
794
795 /* update blots, adjusting the offsets and rotation factors. */
796 static void updateWithFeeling (void)
797 {
798     int n, i, j;
799
800     /* pick a new model if the time is right */
801     itersTillNext--;
802     if (itersTillNext < 0)
803     {
804         itersTillNext = RAND_FLOAT_01 * 1234;
805         setupBlots ();
806         setupSegs ();
807         renderSegs ();
808     }
809
810     /* update the rotation factors by moving them a bit toward the targets */
811     xRot = xRot + (xRotTarget - xRot) * iterAmt; 
812     yRot = yRot + (yRotTarget - yRot) * iterAmt;
813     zRot = zRot + (zRotTarget - zRot) * iterAmt;
814
815     /* similarly the scale factor */
816     curScale = curScale + (scaleTarget - curScale) * iterAmt;
817
818     /* and similarly the light position */
819     lightX = lightX + (lightXTarget - lightX) * iterAmt; 
820     lightY = lightY + (lightYTarget - lightY) * iterAmt; 
821     lightZ = lightZ + (lightZTarget - lightZ) * iterAmt; 
822
823     /* for each blot... */
824     for (n = 0; n < blotCount; n++)
825     {
826         /* add a bit of random jitter to xoff/yoff */
827         for (i = 0; i < 3; i++)
828         {
829             for (j = 0; j < 3; j++)
830             {
831                 FLOAT newOff;
832
833                 newOff = blots[n].xoff[i][j] + RAND_FLOAT_PM1 * nervousness;
834                 if (newOff < -1) newOff = -(newOff + 1) - 1;
835                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
836                 blots[n].xoff[i][j] = newOff;
837
838                 newOff = blots[n].yoff[i][j] + RAND_FLOAT_PM1 * nervousness;
839                 if (newOff < -1) newOff = -(newOff + 1) - 1;
840                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
841                 blots[n].yoff[i][j] = newOff;
842             }
843         }
844     }
845
846     /* depending on random chance, update one or more factors */
847     if (RAND_FLOAT_01 <= eventChance)
848     {
849         int which = RAND_FLOAT_01 * 14;
850         switch (which)
851         {
852             case 0:
853             {
854                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
855                 break;
856             }
857             case 1:
858             {
859                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
860                 break;
861             }
862             case 2:
863             {
864                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
865                 break;
866             }
867             case 3:
868             {
869                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
870                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
871                 break;
872             }
873             case 4:
874             {
875                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
876                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
877                 break;
878             }
879             case 5:
880             {
881                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
882                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
883                 break;
884             }
885             case 6:
886             {
887                 xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
888                 yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
889                 zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
890                 break;
891             }
892             case 7:
893             {
894                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
895                 break;
896             }
897             case 8:
898             {
899                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
900                 break;
901             }
902             case 9:
903             {
904                 centerXOff = RAND_FLOAT_PM1 * maxRadius;
905                 centerYOff = RAND_FLOAT_PM1 * maxRadius;
906                 break;
907             }
908             case 10:
909             {
910                 scaleTarget = 
911                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
912                 break;
913             }
914             case 11:
915             {
916                 curScale = 
917                     RAND_FLOAT_01 * (maxScale - minScale) + minScale;
918                 break;
919             }
920             case 12:
921             {
922                 lightX = RAND_FLOAT_PM1;
923                 lightY = RAND_FLOAT_PM1;
924                 lightZ = RAND_FLOAT_PM1;
925                 break;
926             }
927             case 13:
928             {
929                 lightXTarget = RAND_FLOAT_PM1;
930                 lightYTarget = RAND_FLOAT_PM1;
931                 lightZTarget = RAND_FLOAT_PM1;
932                 break;
933             }
934         }
935     }
936 }
937
938 /* erase segsToErase and draw segsToDraw */
939 static void eraseAndDraw (void)
940 {
941     int n;
942
943     for (n = 0; n < segCount; n++)
944     {
945         LineSegment *seg = &segsToErase[n];
946         XDrawLine (display, drawable, gcs[0], 
947                    seg->x1, seg->y1, seg->x2, seg->y2);
948         seg = &segsToDraw[n];
949         XDrawLine (display, drawable, seg->gc,
950                    seg->x1, seg->y1, seg->x2, seg->y2);
951     }
952
953     if (doubleBuffer)
954     {
955         XCopyArea (display, drawable, window, gcs[0], 0, 0, 
956                    windowWidth, windowHeight, 0, 0);
957     }
958 }
959
960 /* do one iteration */
961 static void oneIteration (void)
962 {
963     /* switch segsToErase and segsToDraw */
964     LineSegment *temp = segsToDraw;
965     segsToDraw = segsToErase;
966     segsToErase = temp;
967
968     /* update the model */
969     updateWithFeeling ();
970
971     /* render new segments */
972     renderSegs ();
973
974     /* erase old segments and draw new ones */
975     eraseAndDraw ();
976 }
977
978 char *progclass = "NerveRot";
979
980 char *defaults [] = {
981     ".background:       black",
982     ".foreground:       white",
983     "*count:            250",
984     "*colors:           4",
985     "*delay:            10000",
986     "*doubleBuffer:     false",
987     "*eventChance:      0.2",
988     "*iterAmt:          0.01",
989     "*lineWidth:        0",
990     "*minScale:         0.6",
991     "*maxScale:         1.75",
992     "*minRadius:        3",
993     "*maxRadius:        25",
994     "*maxNerveRadius:   0.7",
995     "*nervousness:      0.3",
996     0
997 };
998
999 XrmOptionDescRec options [] = {
1000   { "-count",            ".count",          XrmoptionSepArg, 0 },
1001   { "-colors",           ".colors",         XrmoptionSepArg, 0 },
1002   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
1003   { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
1004   { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
1005   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
1006   { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
1007   { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
1008   { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
1009   { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
1010   { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
1011   { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
1012   { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
1013   { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
1014   { 0, 0, 0, 0 }
1015 };
1016
1017 /* initialize the user-specifiable params */
1018 static void initParams (void)
1019 {
1020     int problems = 0;
1021
1022     delay = get_integer_resource ("delay", "Delay");
1023     if (delay < 0)
1024     {
1025         fprintf (stderr, "error: delay must be at least 0\n");
1026         problems = 1;
1027     }
1028     
1029     doubleBuffer = get_boolean_resource ("doubleBuffer", "Boolean");
1030
1031     requestedBlotCount = get_integer_resource ("count", "Count");
1032     if (requestedBlotCount <= 0)
1033     {
1034         fprintf (stderr, "error: count must be at least 0\n");
1035         problems = 1;
1036     }
1037
1038     colorCount = get_integer_resource ("colors", "Colors");
1039     if (colorCount <= 0)
1040     {
1041         fprintf (stderr, "error: colors must be at least 1\n");
1042         problems = 1;
1043     }
1044
1045     lineWidth = get_integer_resource ("lineWidth", "LineWidth");
1046     if (lineWidth < 0)
1047     {
1048         fprintf (stderr, "error: line width must be at least 0\n");
1049         problems = 1;
1050     }
1051
1052     nervousness = get_float_resource ("nervousness", "Float");
1053     if ((nervousness < 0) || (nervousness > 1))
1054     {
1055         fprintf (stderr, "error: nervousness must be in the range 0..1\n");
1056         problems = 1;
1057     }
1058
1059     maxNerveRadius = get_float_resource ("maxNerveRadius", "Float");
1060     if ((maxNerveRadius < 0) || (maxNerveRadius > 1))
1061     {
1062         fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
1063         problems = 1;
1064     }
1065
1066     eventChance = get_float_resource ("eventChance", "Float");
1067     if ((eventChance < 0) || (eventChance > 1))
1068     {
1069         fprintf (stderr, "error: eventChance must be in the range 0..1\n");
1070         problems = 1;
1071     }
1072
1073     iterAmt = get_float_resource ("iterAmt", "Float");
1074     if ((iterAmt < 0) || (iterAmt > 1))
1075     {
1076         fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
1077         problems = 1;
1078     }
1079
1080     minScale = get_float_resource ("minScale", "Float");
1081     if ((minScale < 0) || (minScale > 10))
1082     {
1083         fprintf (stderr, "error: minScale must be in the range 0..10\n");
1084         problems = 1;
1085     }
1086
1087     maxScale = get_float_resource ("maxScale", "Float");
1088     if ((maxScale < 0) || (maxScale > 10))
1089     {
1090         fprintf (stderr, "error: maxScale must be in the range 0..10\n");
1091         problems = 1;
1092     }
1093
1094     if (maxScale < minScale)
1095     {
1096         fprintf (stderr, "error: maxScale must be >= minScale\n");
1097         problems = 1;
1098     }   
1099
1100     minRadius = get_integer_resource ("minRadius", "Integer");
1101     if ((minRadius < 1) || (minRadius > 100))
1102     {
1103         fprintf (stderr, "error: minRadius must be in the range 1..100\n");
1104         problems = 1;
1105     }
1106
1107     maxRadius = get_integer_resource ("maxRadius", "Integer");
1108     if ((maxRadius < 1) || (maxRadius > 100))
1109     {
1110         fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
1111         problems = 1;
1112     }
1113
1114     if (maxRadius < minRadius)
1115     {
1116         fprintf (stderr, "error: maxRadius must be >= minRadius\n");
1117         problems = 1;
1118     }   
1119
1120     if (problems)
1121     {
1122         exit (1);
1123     }
1124 }
1125
1126 /* main function */
1127 void screenhack (Display *dpy, Window win)
1128 {
1129     display = dpy;
1130     window = win;
1131
1132     initParams ();
1133     setup ();
1134
1135     /* make a valid set to erase at first */
1136     renderSegs ();
1137     
1138     for (;;) 
1139     {
1140         oneIteration ();
1141         XSync (dpy, False);
1142         screenhack_handle_events (dpy);
1143         usleep (delay);
1144     }
1145 }