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 */
initBlot(Blot * b,FLOAT x,FLOAT y,FLOAT z)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 */
scaleBlotsToRadius1(struct state * st)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 */
randomlyReorderBlots(struct state * st)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 */
randomlyRotateBlots(struct state * st)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 */
setupBlotsSphere(struct state * st)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 */
setupBlotsCube(struct state * st)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 */
setupBlotsCylinder(struct state * st)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 */
setupBlotsSquiggle(struct state * st)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 */
setupBlotsCubeCorners(struct state * st)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 */
setupBlotsTetrahedron(struct state * st)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 */
setupBlotsSheet(struct state * st)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 */
setupBlotsSwirlyCone(struct state * st)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 */
setupBlotsDuo(struct state * st)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 */
freeBlots(struct state * st)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 */
setupBlots(struct state * st)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 */
setupSegs(struct state * st)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 */
setupColormap(struct state * st,XWindowAttributes * xgwa)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 */
setup(struct state * st)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 */
renderSegs(struct state * st)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. */
updateWithFeeling(struct state * st)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 */
eraseAndDraw(struct state * st)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
nerverot_draw(Display * dpy,Window win,void * closure)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 */
initParams(struct state * st)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 *
nerverot_init(Display * dpy,Window window)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
nerverot_reshape(Display * dpy,Window window,void * closure,unsigned int w,unsigned int h)1291 nerverot_reshape (Display *dpy, Window window, void *closure,
1292                  unsigned int w, unsigned int h)
1293 {
1294 }
1295 
1296 static Bool
nerverot_event(Display * dpy,Window window,void * closure,XEvent * event)1297 nerverot_event (Display *dpy, Window window, void *closure, XEvent *event)
1298 {
1299   return False;
1300 }
1301 
1302 static void
nerverot_free(Display * dpy,Window window,void * closure)1303 nerverot_free (Display *dpy, Window window, void *closure)
1304 {
1305   struct state *st = (struct state *) closure;
1306   int i;
1307   freeBlots (st);
1308   for (i = 0; i <= st->colorCount; i++)
1309     XFreeGC (dpy, st->gcs[i]);
1310   free (st->gcs);
1311   free (st);
1312 }
1313 
1314 
1315 static const char *nerverot_defaults [] = {
1316     ".background:	black",
1317     ".foreground:	white",
1318     "*count:		250",
1319     "*colors:		4",
1320     "*delay:		10000",
1321     "*maxIters:		1200",
1322     "*doubleBuffer:	false",
1323     "*eventChance:      0.2",
1324     "*iterAmt:          0.01",
1325     "*lineWidth:        0",
1326     "*minScale:         0.6",
1327     "*maxScale:         1.75",
1328     "*minRadius:        3",
1329     "*maxRadius:        25",
1330     "*maxNerveRadius:	0.7",
1331     "*nervousness:	0.3",
1332 #ifdef HAVE_MOBILE
1333     "*ignoreRotation:   True",
1334 #endif
1335     0
1336 };
1337 
1338 static XrmOptionDescRec nerverot_options [] = {
1339   { "-count",            ".count",          XrmoptionSepArg, 0 },
1340   { "-colors",           ".colors",         XrmoptionSepArg, 0 },
1341   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
1342   { "-max-iters",        ".maxIters",       XrmoptionSepArg, 0 },
1343   { "-db",               ".doubleBuffer",   XrmoptionNoArg,  "true" },
1344   { "-no-db",            ".doubleBuffer",   XrmoptionNoArg,  "false" },
1345   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
1346   { "-iter-amt",         ".iterAmt",        XrmoptionSepArg, 0 },
1347   { "-line-width",       ".lineWidth",      XrmoptionSepArg, 0 },
1348   { "-min-scale",        ".minScale",       XrmoptionSepArg, 0 },
1349   { "-max-scale",        ".maxScale",       XrmoptionSepArg, 0 },
1350   { "-min-radius",       ".minRadius",      XrmoptionSepArg, 0 },
1351   { "-max-radius",       ".maxRadius",      XrmoptionSepArg, 0 },
1352   { "-max-nerve-radius", ".maxNerveRadius", XrmoptionSepArg, 0 },
1353   { "-nervousness",      ".nervousness",    XrmoptionSepArg, 0 },
1354   { 0, 0, 0, 0 }
1355 };
1356 
1357 
1358 XSCREENSAVER_MODULE ("NerveRot", nerverot)
1359