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