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