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