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