e51621f46a6627ac0327f8cd3a66936d530ea729
[xscreensaver] / hacks / twang.c
1 /* twang, twist around screen bits, v1.3
2  * by Dan Bornstein, danfuzz@milk.com
3  * Copyright (c) 2003 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  * See the included man page for more details.
14  */
15
16 #include <math.h>
17 #include "screenhack.h"
18 #include <X11/Xutil.h>
19
20 #ifdef HAVE_XSHM_EXTENSION
21 #include "xshm.h"
22 #endif
23
24 #define FLOAT double
25
26 /* random float in the range (-1..1) */
27 #define RAND_FLOAT_PM1 \
28         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000) * 2 - 1)
29
30 /* random float in the range (0..1) */
31 #define RAND_FLOAT_01 \
32         (((FLOAT) ((random() >> 8) & 0xffff)) / ((FLOAT) 0x10000))
33
34
35
36 /* parameters that are user configurable */
37
38 /* whether or not to use xshm */
39 #ifdef HAVE_XSHM_EXTENSION
40 static Bool useShm;
41 #endif
42
43 /* delay (usec) between iterations */
44 static int delay;
45
46 /* the maximum number of columns of tiles */
47 static int maxColumns;
48
49 /* the maximum number of rows of tiles */
50 static int maxRows;
51
52 /* the size (width and height) of a tile */
53 static int tileSize;
54
55 /* the width of the border around each tile */
56 static int borderWidth;
57
58 /* the chance, per iteration, of an interesting event happening */
59 static FLOAT eventChance;
60
61 /* friction: the fraction (0..1) by which velocity decreased per iteration */
62 static FLOAT friction;
63
64 /* springiness: the fraction (0..1) of the orientation that turns into 
65  * velocity towards the center */
66 static FLOAT springiness;
67
68 /* transference: the fraction (0..1) of the orientations of orthogonal
69  * neighbors that turns into velocity (in the same direction as the
70  * orientation) */
71 static FLOAT transference;
72
73
74
75 /* non-user-modifiable immutable definitions */
76
77 /* width and height of the window */
78 static int windowWidth;
79 static int windowHeight;
80
81 static Display *display;        /* the display to draw on */
82 static Window window;           /* the window to draw on */
83 static Screen *screen;          /* the screen to draw on */
84
85 static XImage *sourceImage;     /* image source of stuff to draw */
86 static XImage *workImage;       /* work area image, used when rendering */
87 static XImage *backgroundImage; /* image filled with background pixels */
88
89 static GC backgroundGC;         /* GC for the background color */
90 static GC foregroundGC;         /* GC for the foreground color */
91 static GC borderGC;             /* GC for the border color */
92 unsigned long backgroundPixel;  /* background color as a pixel value */
93 unsigned long borderPixel;      /* border color as a pixel value */
94
95 #ifdef HAVE_XSHM_EXTENSION
96 XShmSegmentInfo shmInfo;
97 #endif
98
99
100
101 /* the model */
102
103 typedef struct
104 {
105     int x;        /* x coordinate of the center of the tile */
106     int y;        /* y coordinate of the center of the tile */
107     FLOAT angle;  /* angle of the tile (-pi..pi) */
108     FLOAT zoom;   /* log of the zoom of the tile (-1..1) */
109     FLOAT vAngle; /* angular velocity (-pi/4..pi/4) */
110     FLOAT vZoom;  /* zoomular velocity (-0.25..0.25) */
111 }
112 Tile;
113
114 static Tile *tiles;  /* array of tiles (left->right, top->bottom, row major) */
115 static int rows;     /* number of rows of tiles */
116 static int columns;  /* number of columns of tiles */
117
118 static Tile **sortedTiles; /* array of tile pointers, sorted by zoom */
119 static int tileCount;     /* total number of tiles */
120
121 #define TILE_AT(col,row) (&tiles[(row) * columns + (col)])
122
123 #define MAX_VANGLE (M_PI / 4.0)
124 #define MAX_VZOOM 0.25
125
126 #define RAND_ANGLE (RAND_FLOAT_PM1 * M_PI)
127 #define RAND_ZOOM (RAND_FLOAT_PM1)
128 #define RAND_VANGLE (RAND_FLOAT_PM1 * MAX_VANGLE)
129 #define RAND_VZOOM (RAND_FLOAT_PM1 * MAX_VZOOM)
130
131
132
133 /*
134  * overall setup stuff
135  */
136
137 /* grab the source image */
138 static void grabImage (XWindowAttributes *xwa)
139 {
140     XFillRectangle (display, window, backgroundGC, 0, 0, 
141                     windowWidth, windowHeight);
142     backgroundImage = 
143         XGetImage (display, window, 0, 0, windowWidth, windowHeight,
144                    ~0L, ZPixmap);
145
146     load_random_image (screen, window, window);
147     sourceImage = XGetImage (display, window, 0, 0, windowWidth, windowHeight,
148                              ~0L, ZPixmap);
149
150 #ifdef HAVE_XSHM_EXTENSION
151     workImage = NULL;
152     if (useShm) 
153     {
154         workImage = create_xshm_image (display, xwa->visual, xwa->depth,
155                                        ZPixmap, 0, &shmInfo, 
156                                        windowWidth, windowHeight);
157         if (!workImage) 
158         {
159             useShm = False;
160             fprintf (stderr, "create_xshm_image failed\n");
161         }
162     }
163
164     if (workImage == NULL)
165 #endif /* HAVE_XSHM_EXTENSION */
166
167         /* just use XSubImage to acquire the right visual, depth, etc;
168          * easier than the other alternatives */
169         workImage = XSubImage (sourceImage, 0, 0, windowWidth, windowHeight);
170 }
171
172 /* set up the system */
173 static void setup (void)
174 {
175     XWindowAttributes xgwa;
176     XGCValues gcv;
177
178     XGetWindowAttributes (display, window, &xgwa);
179
180     screen = xgwa.screen;
181     windowWidth = xgwa.width;
182     windowHeight = xgwa.height;
183
184     gcv.line_width = borderWidth;
185     gcv.foreground = get_pixel_resource ("borderColor", "BorderColor",
186                                          display, xgwa.colormap);
187     borderPixel = gcv.foreground;
188     borderGC = XCreateGC (display, window, GCForeground | GCLineWidth, 
189                           &gcv);
190
191     gcv.foreground = get_pixel_resource ("background", "Background",
192                                          display, xgwa.colormap);
193     backgroundPixel = gcv.foreground;
194     backgroundGC = XCreateGC (display, window, GCForeground, &gcv);
195
196     gcv.foreground = get_pixel_resource ("foreground", "Foreground",
197                                          display, xgwa.colormap);
198     foregroundGC = XCreateGC (display, window, GCForeground, &gcv);
199
200     grabImage (&xgwa);
201 }
202
203
204
205 /*
206  * the simulation
207  */
208
209 /* event: randomize all the angular velocities */
210 static void randomizeAllAngularVelocities (void)
211 {
212     int c;
213     int r;
214
215     for (r = 0; r < rows; r++)
216     {
217         for (c = 0; c < columns; c++)
218         {
219             TILE_AT (c, r)->vAngle = RAND_VANGLE;
220         }
221     }
222 }
223
224 /* event: randomize all the zoomular velocities */
225 static void randomizeAllZoomularVelocities (void)
226 {
227     int c;
228     int r;
229
230     for (r = 0; r < rows; r++)
231     {
232         for (c = 0; c < columns; c++)
233         {
234             TILE_AT (c, r)->vZoom = RAND_VZOOM;
235         }
236     }
237 }
238
239 /* event: randomize all the velocities */
240 static void randomizeAllVelocities (void)
241 {
242     randomizeAllAngularVelocities ();
243     randomizeAllZoomularVelocities ();
244 }
245
246 /* event: randomize all the angular orientations */
247 static void randomizeAllAngularOrientations (void)
248 {
249     int c;
250     int r;
251
252     for (r = 0; r < rows; r++)
253     {
254         for (c = 0; c < columns; c++)
255         {
256             TILE_AT (c, r)->angle = RAND_ANGLE;
257         }
258     }
259 }
260
261 /* event: randomize all the zoomular orientations */
262 static void randomizeAllZoomularOrientations (void)
263 {
264     int c;
265     int r;
266
267     for (r = 0; r < rows; r++)
268     {
269         for (c = 0; c < columns; c++)
270         {
271             TILE_AT (c, r)->zoom = RAND_ZOOM;
272         }
273     }
274 }
275
276 /* event: randomize all the orientations */
277 static void randomizeAllOrientations (void)
278 {
279     randomizeAllAngularOrientations ();
280     randomizeAllZoomularOrientations ();
281 }
282
283 /* event: randomize everything */
284 static void randomizeEverything (void)
285 {
286     randomizeAllVelocities ();
287     randomizeAllOrientations ();
288 }
289
290 /* event: pick one tile and randomize all its stats */
291 static void randomizeOneTile (void)
292 {
293     int c = RAND_FLOAT_01 * columns;
294     int r = RAND_FLOAT_01 * rows;
295
296     Tile *t = TILE_AT (c, r);
297     t->angle = RAND_ANGLE;
298     t->zoom = RAND_ZOOM;
299     t->vAngle = RAND_VANGLE;
300     t->vZoom = RAND_VZOOM;
301 }
302
303 /* event: pick one row and randomize everything about each of its tiles */
304 static void randomizeOneRow (void)
305 {
306     int c;
307     int r = RAND_FLOAT_01 * rows;
308
309     for (c = 0; c < columns; c++)
310     {
311         Tile *t = TILE_AT (c, r);
312         t->angle = RAND_ANGLE;
313         t->zoom = RAND_ZOOM;
314         t->vAngle = RAND_VANGLE;
315         t->vZoom = RAND_VZOOM;
316     }
317 }
318
319 /* event: pick one column and randomize everything about each of its tiles */
320 static void randomizeOneColumn (void)
321 {
322     int c = RAND_FLOAT_01 * columns;
323     int r;
324
325     for (r = 0; r < rows; r++)
326     {
327         Tile *t = TILE_AT (c, r);
328         t->angle = RAND_ANGLE;
329         t->zoom = RAND_ZOOM;
330         t->vAngle = RAND_VANGLE;
331         t->vZoom = RAND_VZOOM;
332     }
333 }
334
335 /* do model event processing */
336 static void modelEvents (void)
337 {
338     int which;
339
340     if (RAND_FLOAT_01 > eventChance)
341     {
342         return;
343     }
344
345     which = RAND_FLOAT_01 * 10;
346
347     switch (which)
348     {
349         case 0: randomizeAllAngularVelocities ();    break;
350         case 1: randomizeAllZoomularVelocities ();   break;
351         case 2: randomizeAllVelocities ();           break;
352         case 3: randomizeAllAngularOrientations ();  break;
353         case 4: randomizeAllZoomularOrientations (); break;
354         case 5: randomizeAllOrientations ();         break;
355         case 6: randomizeEverything ();              break;
356         case 7: randomizeOneTile ();                 break;
357         case 8: randomizeOneColumn ();               break;
358         case 9: randomizeOneRow ();                  break;
359     }
360 }
361
362 /* update the model for one iteration */
363 static void updateModel (void)
364 {
365     int r;
366     int c;
367
368     /* for each tile, decrease its velocities according to the friction,
369      * and increase them based on its current orientation and the orientations
370      * of its orthogonal neighbors */
371     for (r = 0; r < rows; r++)
372     {
373         for (c = 0; c < columns; c++)
374         {
375             Tile *t = TILE_AT (c, r);
376             FLOAT a = t->angle;
377             FLOAT z = t->zoom;
378             FLOAT va = t->vAngle;
379             FLOAT vz = t->vZoom;
380
381             va -= t->angle * springiness;
382             vz -= t->zoom * springiness;
383
384             if (c > 0)
385             {
386                 Tile *t2 = TILE_AT (c - 1, r);
387                 va += (t2->angle - a) * transference;
388                 vz += (t2->zoom - z) * transference;
389             }
390
391             if (c < (columns - 1))
392             {
393                 Tile *t2 = TILE_AT (c + 1, r);
394                 va += (t2->angle - a) * transference;
395                 vz += (t2->zoom - z) * transference;
396             }
397
398             if (r > 0)
399             {
400                 Tile *t2 = TILE_AT (c, r - 1);
401                 va += (t2->angle - a) * transference;
402                 vz += (t2->zoom - z) * transference;
403             }
404
405             if (r < (rows - 1))
406             {
407                 Tile *t2 = TILE_AT (c, r + 1);
408                 va += (t2->angle - a) * transference;
409                 vz += (t2->zoom - z) * transference;
410             }
411
412             va *= (1.0 - friction);
413             vz *= (1.0 - friction);
414
415             if (va > MAX_VANGLE) va = MAX_VANGLE;
416             else if (va < -MAX_VANGLE) va = -MAX_VANGLE;
417             t->vAngle = va;
418
419             if (vz > MAX_VZOOM) vz = MAX_VZOOM;
420             else if (vz < -MAX_VZOOM) vz = -MAX_VZOOM;
421             t->vZoom = vz;
422         }
423     }
424
425     /* for each tile, update its orientation based on its velocities */
426     for (r = 0; r < rows; r++)
427     {
428         for (c = 0; c < columns; c++)
429         {
430             Tile *t = TILE_AT (c, r);
431             FLOAT a = t->angle + t->vAngle;
432             FLOAT z = t->zoom + t->vZoom;
433
434             if (a > M_PI) a = M_PI;
435             else if (a < -M_PI) a = -M_PI;
436             t->angle = a;
437
438             if (z > 1.0) z = 1.0;
439             else if (z < -1.0) z = -1.0;
440             t->zoom = z;
441         }
442     }
443 }
444
445 /* the comparator to us to sort the tiles (used immediately below); it'd
446  * sure be nice if C allowed inner functions (or jeebus-forbid *real
447  * closures*!) */
448 static int sortTilesComparator (const void *v1, const void *v2)
449 {
450     Tile *t1 = *(Tile **) v1;
451     Tile *t2 = *(Tile **) v2;
452     
453     if (t1->zoom < t2->zoom)
454     {
455         return -1;
456     }
457
458     if (t1->zoom > t2->zoom)
459     {
460         return 1;
461     }
462
463     return 0;
464 }
465
466 /* sort the tiles in sortedTiles by zoom */
467 static void sortTiles (void)
468 {
469     qsort (sortedTiles, tileCount, sizeof (Tile *), sortTilesComparator);
470 }
471
472 /* render the given tile */
473 static void renderTile (Tile *t)
474 {
475     /* note: the zoom as stored per tile is log-based (centered on 0, with
476      * 0 being no zoom, but the range for zoom-as-drawn is 0.4..2.5,
477      * hence the alteration of t->zoom, below */
478
479     int x, y;
480
481     int tx = t->x;
482     int ty = t->y;
483
484     FLOAT zoom = pow (2.5, t->zoom);
485     FLOAT ang = -t->angle;
486     FLOAT sinAng = sin (ang);
487     FLOAT cosAng = cos (ang);
488
489     FLOAT innerBorder = (tileSize - borderWidth) / 2.0;
490     FLOAT outerBorder = innerBorder + borderWidth;
491
492     int maxCoord = outerBorder * zoom * (fabs (sinAng) + fabs (cosAng));
493     int minX = tx - maxCoord;
494     int maxX = tx + maxCoord;
495     int minY = ty - maxCoord;
496     int maxY = ty + maxCoord;
497
498     FLOAT prey;
499
500     if (minX < 0) minX = 0;
501     if (maxX > windowWidth) maxX = windowWidth;
502     if (minY < 0) minY = 0;
503     if (maxY > windowHeight) maxY = windowHeight;
504
505     sinAng /= zoom;
506     cosAng /= zoom;
507
508     for (y = minY, prey = y - ty; y < maxY; y++, prey++)
509     {
510         FLOAT prex = minX - tx;
511         FLOAT srcx = prex * cosAng - prey * sinAng;
512         FLOAT srcy = prex * sinAng + prey * cosAng;
513
514         for (x = minX; 
515              x < maxX; 
516              x++, srcx += cosAng, srcy += sinAng)
517         {
518             if ((srcx < -innerBorder) || (srcx >= innerBorder) ||
519                 (srcy < -innerBorder) || (srcy >= innerBorder))
520             {
521                 if ((srcx < -outerBorder) || (srcx >= outerBorder) ||
522                     (srcy < -outerBorder) || (srcy >= outerBorder))
523                 {
524                     continue;
525                 }
526                 XPutPixel (workImage, x, y, borderPixel);
527             }
528             else
529             {
530                 unsigned long p = 
531                     XGetPixel (sourceImage, srcx + tx, srcy + ty);
532                 XPutPixel (workImage, x, y, p);
533             }
534         }
535     }
536 }
537
538 /* render and display the current model */
539 static void renderFrame (void)
540 {
541     int n;
542
543     memcpy (workImage->data, backgroundImage->data, 
544             workImage->bytes_per_line * workImage->height);
545
546     sortTiles ();
547
548     for (n = 0; n < tileCount; n++)
549     {
550         renderTile (sortedTiles[n]);
551     }
552
553 #ifdef HAVE_XSHM_EXTENSION
554     if (useShm)
555         XShmPutImage (display, window, backgroundGC, workImage, 0, 0, 0, 0,
556                       windowWidth, windowHeight, False);
557     else
558 #endif /* HAVE_XSHM_EXTENSION */
559         XPutImage (display, window, backgroundGC, workImage, 
560                    0, 0, 0, 0, windowWidth, windowHeight);
561 }
562
563 /* set up the model */
564 static void setupModel (void)
565 {
566     int c;
567     int r;
568
569     int leftX; /* x of the center of the top-left tile */
570     int topY;  /* y of the center of the top-left tile */
571
572     if (tileSize > (windowWidth / 2))
573     {
574         tileSize = windowWidth / 2;
575     }
576
577     if (tileSize > (windowHeight / 2))
578     {
579         tileSize = windowHeight / 2;
580     }
581
582     columns = windowWidth / tileSize;
583     rows = windowHeight / tileSize;
584
585     if ((maxColumns != 0) && (columns > maxColumns))
586     {
587         columns = maxColumns;
588     }
589
590     if ((maxRows != 0) && (rows > maxRows))
591     {
592         rows = maxRows;
593     }
594
595     tileCount = rows * columns;
596
597     leftX = (windowWidth - (columns * tileSize) + tileSize) / 2;
598     topY = (windowHeight - (rows * tileSize) + tileSize) / 2;
599
600     tiles = calloc (tileCount, sizeof (Tile));
601     sortedTiles = calloc (tileCount, sizeof (Tile *));
602
603     for (r = 0; r < rows; r++)
604     {
605         for (c = 0; c < columns; c++)
606         {
607             Tile *t = TILE_AT (c, r);
608             t->x = leftX + c * tileSize;
609             t->y = topY + r * tileSize;
610             sortedTiles[c + r * columns] = t;
611         }
612     }
613
614     randomizeEverything ();
615 }
616
617 /* do one iteration */
618 static void oneIteration (void)
619 {
620     modelEvents ();
621     updateModel ();
622     renderFrame ();
623 }
624
625
626
627 /* main and options and stuff */
628
629 char *progclass = "Twang";
630
631 char *defaults [] = {
632     ".background:       black",
633     ".foreground:       white",
634     "*borderColor:      blue",
635     "*borderWidth:      3",
636     "*delay:            10000",
637     "*eventChance:      0.01",
638     "*friction:         0.05",
639     "*maxColumns:       0",
640     "*maxRows:          0",
641     "*springiness:      0.1",
642     "*tileSize:         120",
643     "*transference:     0.025",
644 #ifdef HAVE_XSHM_EXTENSION
645     "*useSHM: True",
646 #endif
647     0
648 };
649
650 XrmOptionDescRec options [] = {
651   { "-border-color",     ".borderColor",    XrmoptionSepArg, 0 },
652   { "-border-width",     ".borderWidth",    XrmoptionSepArg, 0 },
653   { "-delay",            ".delay",          XrmoptionSepArg, 0 },
654   { "-event-chance",     ".eventChance",    XrmoptionSepArg, 0 },
655   { "-friction",         ".friction",       XrmoptionSepArg, 0 },
656   { "-max-columns",      ".maxColumns",     XrmoptionSepArg, 0 },
657   { "-max-rows",         ".maxRows",        XrmoptionSepArg, 0 },
658   { "-springiness",      ".springiness",    XrmoptionSepArg, 0 },
659   { "-tile-size",        ".tileSize",       XrmoptionSepArg, 0 },
660   { "-transference",     ".transference",   XrmoptionSepArg, 0 },
661 #ifdef HAVE_XSHM_EXTENSION
662   { "-shm",              ".useSHM",         XrmoptionNoArg, "True" },
663   { "-no-shm",           ".useSHM",         XrmoptionNoArg, "False" },
664 #endif
665   { 0, 0, 0, 0 }
666 };
667
668 /* initialize the user-specifiable params */
669 static void initParams (void)
670 {
671     int problems = 0;
672
673     borderWidth = get_integer_resource ("borderWidth", "Integer");
674     if (borderWidth < 0)
675     {
676         fprintf (stderr, "error: border width must be at least 0\n");
677         problems = 1;
678     }
679
680     delay = get_integer_resource ("delay", "Delay");
681     if (delay < 0)
682     {
683         fprintf (stderr, "error: delay must be at least 0\n");
684         problems = 1;
685     }
686
687     eventChance = get_float_resource ("eventChance", "Double");
688     if ((eventChance < 0.0) || (eventChance > 1.0))
689     {
690         fprintf (stderr, "error: eventChance must be in the range 0..1\n");
691         problems = 1;
692     }
693
694     friction = get_float_resource ("friction", "Double");
695     if ((friction < 0.0) || (friction > 1.0))
696     {
697         fprintf (stderr, "error: friction must be in the range 0..1\n");
698         problems = 1;
699     }
700
701     maxColumns = get_integer_resource ("maxColumns", "Integer");
702     if (maxColumns < 0)
703     {
704         fprintf (stderr, "error: max columns must be at least 0\n");
705         problems = 1;
706     }
707
708     maxRows = get_integer_resource ("maxRows", "Integer");
709     if (maxRows < 0)
710     {
711         fprintf (stderr, "error: max rows must be at least 0\n");
712         problems = 1;
713     }
714
715     springiness = get_float_resource ("springiness", "Double");
716     if ((springiness < 0.0) || (springiness > 1.0))
717     {
718         fprintf (stderr, "error: springiness must be in the range 0..1\n");
719         problems = 1;
720     }
721
722     tileSize = get_integer_resource ("tileSize", "Integer");
723     if (tileSize < 1)
724     {
725         fprintf (stderr, "error: tile size must be at least 1\n");
726         problems = 1;
727     }
728     
729     transference = get_float_resource ("transference", "Double");
730     if ((transference < 0.0) || (transference > 1.0))
731     {
732         fprintf (stderr, "error: transference must be in the range 0..1\n");
733         problems = 1;
734     }
735
736 #ifdef HAVE_XSHM_EXTENSION
737     useShm = get_boolean_resource ("useSHM", "Boolean");
738 #endif
739
740     if (problems)
741     {
742         exit (1);
743     }
744 }
745
746 /* main function */
747 void screenhack (Display *dpy, Window win)
748 {
749     display = dpy;
750     window = win;
751
752     initParams ();
753     setup ();
754     setupModel ();
755
756     for (;;) 
757     {
758         oneIteration ();
759         XSync (dpy, False);
760         screenhack_handle_events (dpy);
761         usleep (delay);
762     }
763 }