From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / nerverot.c
1 /* nerverot, nervous rotation of random thingies, v1.4
2  * by Dan Bornstein, danfuzz@milk.com
3  * Copyright (c) 2000-2001 Dan Bornstein. All rights reserved.
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  *
13  * The goal of this screensaver is to be interesting and compelling to
14  * watch, yet induce a state of nervous edginess in the viewer.
15  *
16  * See the included man page for more details.
17  */
18
19 #include <math.h>
20 #include "screenhack.h"
21
22 #define FLOAT double
23
24 /* random float in the range (-1..1) */
25 #define RAND_FLOAT_PM1 \
26         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
27
28 /* random float in the range (0..1) */
29 #define RAND_FLOAT_01 \
30         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
31
32
33 /* 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 (xgwa->screen, xgwa->visual, xgwa->colormap,
803                      h1, s1, v1, h2, s2, v2,
804                      colors + 1, &st->colorCount, False, True, False);
805
806     if (st->colorCount < 1)
807     {
808         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
809         exit (-1);
810     }
811     
812     st->gcs = (GC *) calloc (sizeof (GC), st->colorCount + 1);
813
814     for (n = 0; n <= st->colorCount; n++) 
815     {
816         gcv.foreground = colors[n].pixel;
817         gcv.line_width = st->lineWidth;
818         st->gcs[n] = XCreateGC (st->dpy, st->window, GCForeground | GCLineWidth, &gcv);
819     }
820
821     free (colors);
822 }
823
824
825
826 /*
827  * overall setup stuff
828  */
829
830 /* set up the system */
831 static void setup (struct state *st)
832 {
833     XWindowAttributes xgwa;
834
835     XGetWindowAttributes (st->dpy, st->window, &xgwa);
836
837     st->windowWidth = xgwa.width;
838     st->windowHeight = xgwa.height;
839     st->centerX = st->windowWidth / 2;
840     st->centerY = st->windowHeight / 2;
841     st->baseScale = (xgwa.height < xgwa.width) ? xgwa.height : xgwa.width;
842
843     if (st->doubleBuffer)
844     {
845         st->drawable = XCreatePixmap (st->dpy, st->window, xgwa.width, xgwa.height,
846                                   xgwa.depth);
847     }
848     else
849     {
850         st->drawable = st->window;
851     }
852
853     setupColormap (st, &xgwa);
854     setupBlots (st);
855     setupSegs (st);
856
857     /* set up the initial rotation, scale, and light values as random, but
858      * with the targets equal to where it is */
859     st->xRot = st->xRotTarget = RAND_FLOAT_01 * M_PI;
860     st->yRot = st->yRotTarget = RAND_FLOAT_01 * M_PI;
861     st->zRot = st->zRotTarget = RAND_FLOAT_01 * M_PI;
862     st->curScale = st->scaleTarget = RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
863     st->lightX = st->lightXTarget = RAND_FLOAT_PM1;
864     st->lightY = st->lightYTarget = RAND_FLOAT_PM1;
865     st->lightZ = st->lightZTarget = RAND_FLOAT_PM1;
866
867     st->itersTillNext = RAND_FLOAT_01 * st->maxIters;
868 }
869
870
871
872 /*
873  * the simulation
874  */
875
876 /* "render" the blots into segsToDraw, with the current rotation factors */
877 static void renderSegs (struct state *st)
878 {
879     int n;
880     int m = 0;
881
882     /* rotation factors */
883     FLOAT sinX = sin (st->xRot);
884     FLOAT cosX = cos (st->xRot);
885     FLOAT sinY = sin (st->yRot);
886     FLOAT cosY = cos (st->yRot);
887     FLOAT sinZ = sin (st->zRot);
888     FLOAT cosZ = cos (st->zRot);
889
890     for (n = 0; n < st->blotCount; n++)
891     {
892         Blot *b = &st->blots[n];
893         int i, j;
894         int baseX, baseY;
895         FLOAT radius;
896         int x[3][3];
897         int y[3][3];
898         int color;
899
900         FLOAT x1 = st->blots[n].x;
901         FLOAT y1 = st->blots[n].y;
902         FLOAT z1 = st->blots[n].z;
903         FLOAT x2, y2, z2;
904
905         /* rotate on z axis */
906         x2 = x1 * cosZ - y1 * sinZ;
907         y2 = x1 * sinZ + y1 * cosZ;
908         z2 = z1;
909
910         /* rotate on x axis */
911         y1 = y2 * cosX - z2 * sinX;
912         z1 = y2 * sinX + z2 * cosX;
913         x1 = x2;
914
915         /* rotate on y axis */
916         z2 = z1 * cosY - x1 * sinY;
917         x2 = z1 * sinY + x1 * cosY;
918         y2 = y1;
919
920         /* the color to draw is based on the distance from the light of
921          * the post-rotation blot */
922         x1 = x2 - st->lightX;
923         y1 = y2 - st->lightY;
924         z1 = z2 - st->lightZ;
925         color = 1 + (x1 * x1 + y1 * y1 + z1 * z1) / 4 * st->colorCount;
926         if (color > st->colorCount)
927         {
928             color = st->colorCount;
929         }
930
931         /* set up the base screen coordinates for drawing */
932         baseX = x2 / 2 * st->baseScale * st->curScale + st->centerX + st->centerXOff;
933         baseY = y2 / 2 * st->baseScale * st->curScale + st->centerY + st->centerYOff;
934         
935         radius = (z2 + 1) / 2 * (st->maxRadius - st->minRadius) + st->minRadius;
936
937         for (i = 0; i < 3; i++)
938         {
939             for (j = 0; j < 3; j++)
940             {
941                 x[i][j] = baseX + 
942                     ((i - 1) + (b->xoff[i][j] * st->maxNerveRadius)) * radius;
943                 y[i][j] = baseY + 
944                     ((j - 1) + (b->yoff[i][j] * st->maxNerveRadius)) * radius;
945             }
946         }
947
948         for (i = 1; i < blotShapeCount; i++)
949         {
950             st->segsToDraw[m].gc = st->gcs[color];
951             st->segsToDraw[m].x1 = x[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
952             st->segsToDraw[m].y1 = y[blotShape[i-1].x + 1][blotShape[i-1].y + 1];
953             st->segsToDraw[m].x2 = x[blotShape[i].x   + 1][blotShape[i].y   + 1];
954             st->segsToDraw[m].y2 = y[blotShape[i].x   + 1][blotShape[i].y   + 1];
955             m++;
956         }
957     }
958 }
959
960 /* update blots, adjusting the offsets and rotation factors. */
961 static void updateWithFeeling (struct state *st)
962 {
963     int n, i, j;
964
965     /* pick a new model if the time is right */
966     st->itersTillNext--;
967     if (st->itersTillNext < 0)
968     {
969         st->itersTillNext = RAND_FLOAT_01 * st->maxIters;
970         setupBlots (st);
971         setupSegs (st);
972         renderSegs (st);
973     }
974
975     /* update the rotation factors by moving them a bit toward the targets */
976     st->xRot = st->xRot + (st->xRotTarget - st->xRot) * st->iterAmt; 
977     st->yRot = st->yRot + (st->yRotTarget - st->yRot) * st->iterAmt;
978     st->zRot = st->zRot + (st->zRotTarget - st->zRot) * st->iterAmt;
979
980     /* similarly the scale factor */
981     st->curScale = st->curScale + (st->scaleTarget - st->curScale) * st->iterAmt;
982
983     /* and similarly the light position */
984     st->lightX = st->lightX + (st->lightXTarget - st->lightX) * st->iterAmt; 
985     st->lightY = st->lightY + (st->lightYTarget - st->lightY) * st->iterAmt; 
986     st->lightZ = st->lightZ + (st->lightZTarget - st->lightZ) * st->iterAmt; 
987
988     /* for each blot... */
989     for (n = 0; n < st->blotCount; n++)
990     {
991         /* add a bit of random jitter to xoff/yoff */
992         for (i = 0; i < 3; i++)
993         {
994             for (j = 0; j < 3; j++)
995             {
996                 FLOAT newOff;
997
998                 newOff = st->blots[n].xoff[i][j] + RAND_FLOAT_PM1 * st->nervousness;
999                 if (newOff < -1) newOff = -(newOff + 1) - 1;
1000                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
1001                 st->blots[n].xoff[i][j] = newOff;
1002
1003                 newOff = st->blots[n].yoff[i][j] + RAND_FLOAT_PM1 * st->nervousness;
1004                 if (newOff < -1) newOff = -(newOff + 1) - 1;
1005                 else if (newOff > 1) newOff = -(newOff - 1) + 1;
1006                 st->blots[n].yoff[i][j] = newOff;
1007             }
1008         }
1009     }
1010
1011     /* depending on random chance, update one or more factors */
1012     if (RAND_FLOAT_01 <= st->eventChance)
1013     {
1014         int which = RAND_FLOAT_01 * 14;
1015         switch (which)
1016         {
1017             case 0:
1018             {
1019                 st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1020                 break;
1021             }
1022             case 1:
1023             {
1024                 st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1025                 break;
1026             }
1027             case 2:
1028             {
1029                 st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1030                 break;
1031             }
1032             case 3:
1033             {
1034                 st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1035                 st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1036                 break;
1037             }
1038             case 4:
1039             {
1040                 st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1041                 st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1042                 break;
1043             }
1044             case 5:
1045             {
1046                 st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1047                 st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1048                 break;
1049             }
1050             case 6:
1051             {
1052                 st->xRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1053                 st->yRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1054                 st->zRotTarget = RAND_FLOAT_PM1 * M_PI * 2;
1055                 break;
1056             }
1057             case 7:
1058             {
1059                 st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
1060                 break;
1061             }
1062             case 8:
1063             {
1064                 st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
1065                 break;
1066             }
1067             case 9:
1068             {
1069                 st->centerXOff = RAND_FLOAT_PM1 * st->maxRadius;
1070                 st->centerYOff = RAND_FLOAT_PM1 * st->maxRadius;
1071                 break;
1072             }
1073             case 10:
1074             {
1075                 st->scaleTarget = 
1076                     RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
1077                 break;
1078             }
1079             case 11:
1080             {
1081                 st->curScale = 
1082                     RAND_FLOAT_01 * (st->maxScale - st->minScale) + st->minScale;
1083                 break;
1084             }
1085             case 12:
1086             {
1087                 st->lightX = RAND_FLOAT_PM1;
1088                 st->lightY = RAND_FLOAT_PM1;
1089                 st->lightZ = RAND_FLOAT_PM1;
1090                 break;
1091             }
1092             case 13:
1093             {
1094                 st->lightXTarget = RAND_FLOAT_PM1;
1095                 st->lightYTarget = RAND_FLOAT_PM1;
1096                 st->lightZTarget = RAND_FLOAT_PM1;
1097                 break;
1098             }
1099         }
1100     }
1101 }
1102
1103 /* erase segsToErase and draw segsToDraw */
1104 static void eraseAndDraw (struct state *st)
1105 {
1106     int n;
1107
1108     if (st->doubleBuffer)
1109       XFillRectangle (st->dpy, st->drawable, st->gcs[0], 0, 0, 
1110                       st->windowWidth, st->windowHeight);
1111     else
1112       XClearWindow (st->dpy, st->drawable);
1113
1114     for (n = 0; n < st->segCount; n++)
1115     {
1116         LineSegment *seg = &st->segsToErase[n];
1117 #ifdef HAVE_JWXYZ       /* Don't second-guess Quartz's double-buffering */
1118         XDrawLine (st->dpy, st->drawable, st->gcs[0], 
1119                    seg->x1, seg->y1, seg->x2, seg->y2);
1120 #endif
1121         seg = &st->segsToDraw[n];
1122         XDrawLine (st->dpy, st->drawable, seg->gc,
1123                    seg->x1, seg->y1, seg->x2, seg->y2);
1124     }
1125
1126     if (st->doubleBuffer)
1127     {
1128         XCopyArea (st->dpy, st->drawable, st->window, st->gcs[0], 0, 0, 
1129                    st->windowWidth, st->windowHeight, 0, 0);
1130     }
1131 }
1132
1133 /* do one iteration */
1134 static unsigned long
1135 nerverot_draw (Display *dpy, Window win, void *closure)
1136 {
1137   struct state *st = (struct state *) closure;
1138     /* switch segsToErase and segsToDraw */
1139     LineSegment *temp = st->segsToDraw;
1140     st->segsToDraw = st->segsToErase;
1141     st->segsToErase = temp;
1142
1143     /* update the model */
1144     updateWithFeeling (st);
1145
1146     /* render new segments */
1147     renderSegs (st);
1148
1149     /* erase old segments and draw new ones */
1150     eraseAndDraw (st);
1151
1152     return st->delay;
1153 }
1154
1155 /* initialize the user-specifiable params */
1156 static void initParams (struct state *st)
1157 {
1158     int problems = 0;
1159
1160     st->delay = get_integer_resource (st->dpy, "delay", "Delay");
1161     if (st->delay < 0)
1162     {
1163         fprintf (stderr, "error: delay must be at least 0\n");
1164         problems = 1;
1165     }
1166     
1167     st->maxIters = get_integer_resource (st->dpy, "maxIters", "Integer");
1168     if (st->maxIters < 0)
1169     {
1170         fprintf (stderr, "error: maxIters must be at least 0\n");
1171         problems = 1;
1172     }
1173     
1174     st->doubleBuffer = get_boolean_resource (st->dpy, "doubleBuffer", "Boolean");
1175
1176 # ifdef HAVE_JWXYZ      /* Don't second-guess Quartz's double-buffering */
1177     st->doubleBuffer = False;
1178 # endif
1179
1180     st->requestedBlotCount = get_integer_resource (st->dpy, "count", "Count");
1181     if (st->requestedBlotCount <= 0)
1182     {
1183         fprintf (stderr, "error: count must be at least 0\n");
1184         problems = 1;
1185     }
1186
1187     st->colorCount = get_integer_resource (st->dpy, "colors", "Colors");
1188     if (st->colorCount <= 0)
1189     {
1190         fprintf (stderr, "error: colors must be at least 1\n");
1191         problems = 1;
1192     }
1193
1194     st->lineWidth = get_integer_resource (st->dpy, "lineWidth", "LineWidth");
1195     if (st->lineWidth < 0)
1196     {
1197         fprintf (stderr, "error: line width must be at least 0\n");
1198         problems = 1;
1199     }
1200
1201     st->nervousness = get_float_resource (st->dpy, "nervousness", "Float");
1202     if ((st->nervousness < 0) || (st->nervousness > 1))
1203     {
1204         fprintf (stderr, "error: nervousness must be in the range 0..1\n");
1205         problems = 1;
1206     }
1207
1208     st->maxNerveRadius = get_float_resource (st->dpy, "maxNerveRadius", "Float");
1209     if ((st->maxNerveRadius < 0) || (st->maxNerveRadius > 1))
1210     {
1211         fprintf (stderr, "error: maxNerveRadius must be in the range 0..1\n");
1212         problems = 1;
1213     }
1214
1215     st->eventChance = get_float_resource (st->dpy, "eventChance", "Float");
1216     if ((st->eventChance < 0) || (st->eventChance > 1))
1217     {
1218         fprintf (stderr, "error: eventChance must be in the range 0..1\n");
1219         problems = 1;
1220     }
1221
1222     st->iterAmt = get_float_resource (st->dpy, "iterAmt", "Float");
1223     if ((st->iterAmt < 0) || (st->iterAmt > 1))
1224     {
1225         fprintf (stderr, "error: iterAmt must be in the range 0..1\n");
1226         problems = 1;
1227     }
1228
1229     st->minScale = get_float_resource (st->dpy, "minScale", "Float");
1230     if ((st->minScale < 0) || (st->minScale > 10))
1231     {
1232         fprintf (stderr, "error: minScale must be in the range 0..10\n");
1233         problems = 1;
1234     }
1235
1236     st->maxScale = get_float_resource (st->dpy, "maxScale", "Float");
1237     if ((st->maxScale < 0) || (st->maxScale > 10))
1238     {
1239         fprintf (stderr, "error: maxScale must be in the range 0..10\n");
1240         problems = 1;
1241     }
1242
1243     if (st->maxScale < st->minScale)
1244     {
1245         fprintf (stderr, "error: maxScale must be >= minScale\n");
1246         problems = 1;
1247     }   
1248
1249     st->minRadius = get_integer_resource (st->dpy, "minRadius", "Integer");
1250     if ((st->minRadius < 1) || (st->minRadius > 100))
1251     {
1252         fprintf (stderr, "error: minRadius must be in the range 1..100\n");
1253         problems = 1;
1254     }
1255
1256     st->maxRadius = get_integer_resource (st->dpy, "maxRadius", "Integer");
1257     if ((st->maxRadius < 1) || (st->maxRadius > 100))
1258     {
1259         fprintf (stderr, "error: maxRadius must be in the range 1..100\n");
1260         problems = 1;
1261     }
1262
1263     if (st->maxRadius < st->minRadius)
1264     {
1265         fprintf (stderr, "error: maxRadius must be >= minRadius\n");
1266         problems = 1;
1267     }   
1268
1269     if (problems)
1270     {
1271         exit (1);
1272     }
1273 }
1274
1275 static void *
1276 nerverot_init (Display *dpy, Window window)
1277 {
1278   struct state *st = (struct state *) calloc (1, sizeof(*st));
1279   st->dpy = dpy;
1280   st->window = window;
1281
1282     initParams (st);
1283     setup (st);
1284
1285     /* make a valid set to erase at first */
1286     renderSegs (st);
1287     return st;
1288 }
1289
1290 static void
1291 nerverot_reshape (Display *dpy, Window window, void *closure, 
1292                  unsigned int w, unsigned int h)
1293 {
1294 }
1295
1296 static Bool
1297 nerverot_event (Display *dpy, Window window, void *closure, XEvent *event)
1298 {
1299   return False;
1300 }
1301
1302 static void
1303 nerverot_free (Display *dpy, Window window, void *closure)
1304 {
1305   struct state *st = (struct state *) closure;
1306   freeBlots (st);
1307   free (st);
1308 }
1309
1310
1311 static const char *nerverot_defaults [] = {
1312     ".background:       black",
1313     ".foreground:       white",
1314     "*count:            250",
1315     "*colors:           4",
1316     "*delay:            10000",
1317     "*maxIters:         1200",
1318     "*doubleBuffer:     false",
1319     "*eventChance:      0.2",
1320     "*iterAmt:          0.01",
1321     "*lineWidth:        0",
1322     "*minScale:         0.6",
1323     "*maxScale:         1.75",
1324     "*minRadius:        3",
1325     "*maxRadius:        25",
1326     "*maxNerveRadius:   0.7",
1327     "*nervousness:      0.3",
1328 #ifdef HAVE_MOBILE
1329     "*ignoreRotation:   True",
1330 #endif
1331     0
1332 };
1333
1334 static XrmOptionDescRec nerverot_options [] = {
1335   { "-count",            ".count",          XrmoptionSepArg, 0 },
1336   { "-colors",           ".colors",         XrmoptionSepArg, 0 },
1337   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
1338   { "-max-iters",        ".maxIters",       XrmoptionSepArg, 0 },
1339   { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
1340   { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
1341   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
1342   { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
1343   { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
1344   { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
1345   { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
1346   { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
1347   { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
1348   { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
1349   { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
1350   { 0, 0, 0, 0 }
1351 };
1352
1353
1354 XSCREENSAVER_MODULE ("NerveRot", nerverot)