http://www.ibiblio.org/pub/historic-linux/ftp-archives/sunsite.unc.edu/Sep-29-1996...
[xscreensaver] / hacks / attraction.c
1 /* xscreensaver, Copyright (c) 1992, 1995 Jamie Zawinski <jwz@netscape.com>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
13    a little like the strong and weak electromagnetic forces.  Derived from
14    a Lispm screensaver by John Pezaris <pz@mit.edu>.  Mouse control and
15    viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.
16
17    John sez:
18
19    The simulation started out as a purely accurate gravitational simulation,
20    but, with constant simulation step size, I quickly realized the field being
21    simulated while grossly gravitational was, in fact, non-conservative.  It
22    also had the rather annoying behavior of dealing very badly with colliding
23    orbs.  Therefore, I implemented a negative-gravity region (with two
24    thresholds; as I read your code, you only implemented one) to prevent orbs
25    from every coming too close together, and added a viscosity factor if the
26    speed of any orb got too fast.  This provides a nice stable system with
27    interesting behavior.
28
29    I had experimented with a number of fields including the van der Waals
30    force (very interesting orbiting behavior) and 1/r^3 gravity (not as
31    interesting as 1/r^2).  An even normal viscosity (rather than the
32    thresholded version to bleed excess energy) is also not interesting.
33    The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
34    interesting -- the orbs never collided and the threshold viscosity fixed
35    the non-conservational problem.
36
37    Philip sez:
38    > An even normal viscosity (rather than the thresholded version to
39    > bleed excess energy) is also not interesting.
40
41    unless you make about 200 points.... set the viscosity to about .8
42    and drag the mouse through it.   it makes a nice wave which travels
43    through the field.
44  */
45
46 #include <stdio.h>
47 #include <math.h>
48 #include "screenhack.h"
49 #include "spline.h"
50
51 struct ball {
52   float x, y;
53   float vx, vy;
54   float dx, dy;
55   float mass;
56   int size;
57   XColor color;
58   int hue;
59 };
60
61 static unsigned int default_fg_pixel;
62 static struct ball *balls;
63 static int npoints;
64 static int threshold;
65 static int delay;
66 static int global_size;
67 static int segments;
68 static Bool glow_p;
69 static Bool orbit_p;
70 static XPoint *point_stack;
71 static int point_stack_size, point_stack_fp, pixel_stack_fp, pixel_stack_size;
72 static unsigned long *pixel_stack;
73 static unsigned int color_shift;
74
75 /*flip mods for mouse interaction*/
76 static Bool mouse_p;
77 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
78 static float viscosity;
79
80 static enum object_mode {
81   ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
82   tail_mode
83 } mode;
84
85 static enum color_mode {
86   cycle_mode, random_mode
87 } cmode;
88
89 static GC draw_gc, erase_gc;
90
91 #define MAX_SIZE 16
92
93 #define min(a,b) ((a)<(b)?(a):(b))
94 #define max(a,b) ((a)>(b)?(a):(b))
95
96 static void
97 init_balls (dpy, window)
98      Display *dpy;
99      Window window;
100 {
101   int i;
102   XWindowAttributes xgwa;
103   XGCValues gcv;
104   int xlim, ylim, midx, midy, r, vx, vy;
105   double th;
106   Colormap cmap;
107   char *mode_str;
108   XGetWindowAttributes (dpy, window, &xgwa);
109   xlim = xgwa.width;
110   ylim = xgwa.height;
111   cmap = xgwa.colormap;
112   midx = xlim/2;
113   midy = ylim/2;
114   r = get_integer_resource ("radius", "Integer");
115   if (r <= 0 || r > min (xlim/2, ylim/2))
116     r = min (xlim/2, ylim/2) - 50;
117   vx = get_integer_resource ("vx", "Integer");
118   vy = get_integer_resource ("vy", "Integer");
119   npoints = get_integer_resource ("points", "Integer");
120   if (npoints < 1)
121     npoints = 3 + (random () % 5);
122   balls = (struct ball *) malloc (npoints * sizeof (struct ball));
123   segments = get_integer_resource ("segments", "Integer");
124   if (segments < 0) segments = 1;
125   threshold = get_integer_resource ("threshold", "Integer");
126   if (threshold < 0) threshold = 0;
127   delay = get_integer_resource ("delay", "Integer");
128     if (delay < 0) delay = 0;
129   global_size = get_integer_resource ("size", "Integer");
130   if (global_size < 0) global_size = 0;
131   glow_p = get_boolean_resource ("glow", "Boolean");
132   orbit_p = get_boolean_resource ("orbit", "Boolean");
133   color_shift = get_integer_resource ("colorShift", "Integer");
134   if (color_shift >= 360) color_shift = 5;
135
136   /*flip mods for mouse interaction*/
137   mouse_p = get_boolean_resource ("mouse", "Boolean");
138   mouse_mass = get_integer_resource ("mouseSize", "Integer");
139   mouse_mass =  mouse_mass *  mouse_mass *10;
140
141   viscosity = get_float_resource ("viscosity", "Float");
142
143   mode_str = get_string_resource ("mode", "Mode");
144   if (! mode_str) mode = ball_mode;
145   else if (!strcmp (mode_str, "balls")) mode = ball_mode;
146   else if (!strcmp (mode_str, "lines")) mode = line_mode;
147   else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
148   else if (!strcmp (mode_str, "tails")) mode = tail_mode;
149   else if (!strcmp (mode_str, "splines")) mode = spline_mode;
150   else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
151   else {
152     fprintf (stderr,
153              "%s: mode must be balls, lines, tails, polygons, splines, or\n\
154         filled-splines, not \"%s\"\n",
155              progname, mode_str);
156     exit (1);
157   }
158
159   mode_str = get_string_resource ("colorMode", "ColorMode");
160   if (! mode_str) cmode = cycle_mode;
161   else if (!strcmp (mode_str, "cycle")) cmode = cycle_mode;
162   else if (!strcmp (mode_str, "random")) cmode = random_mode;
163   else {
164     fprintf (stderr, "%s: colorMode must be cycle or random, not \"%s\"\n",
165              progname, mode_str);
166     exit (1);
167   }
168
169   if (mode != ball_mode && mode != tail_mode) glow_p = False;
170   
171   if (mode == polygon_mode && npoints < 3)
172     mode = line_mode;
173
174   if (mode != ball_mode)
175     {
176       int size = (segments ? segments : 1);
177       point_stack_size = size * (npoints + 1);
178       point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
179       point_stack_fp = 0;
180       if (segments > 0)
181         pixel_stack_size = segments;
182       else
183         pixel_stack_size = (360 / color_shift);
184       pixel_stack = (unsigned long *)
185         calloc (pixel_stack_size, sizeof (unsigned int));
186       pixel_stack_fp = 0;
187     }
188
189   gcv.line_width = (mode == tail_mode
190                     ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
191                     : 1);
192   gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
193
194   gcv.foreground = default_fg_pixel =
195     get_pixel_resource ("foreground", "Foreground", dpy, cmap);
196   draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
197   gcv.foreground = get_pixel_resource ("background", "Background", dpy, cmap);
198   erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
199
200   if (!mono_p && mode != ball_mode)
201     for (i = 0; i < pixel_stack_size; i++)
202       {
203         XColor color;
204         color.pixel = default_fg_pixel;
205         XQueryColor (dpy, cmap, &color);
206         if (!XAllocColor (dpy, cmap, &color)) abort ();
207         pixel_stack [i] = color.pixel;
208       }
209
210 #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
211
212   if (orbit_p && !global_size)
213     /* To orbit, all objects must be the same mass, or the math gets
214        really hairy... */
215     global_size = rand_size ();
216
217   th = frand (M_PI+M_PI);
218   for (i = 0; i < npoints; i++)
219     {
220       int new_size = (global_size ? global_size : rand_size ());
221       balls [i].dx = 0;
222       balls [i].dy = 0;
223       balls [i].size = new_size;
224       balls [i].mass = (new_size * new_size * 10);
225       balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
226       balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
227       if (! orbit_p)
228         {
229           balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
230           balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
231         }
232       balls [i].color.pixel = default_fg_pixel;
233       balls [i].color.flags = DoRed | DoGreen | DoBlue;
234       if (!mono_p)
235         {
236           if (i != 0 && (glow_p || mode != ball_mode))
237             balls [i].hue = balls [0].hue;
238           else
239             balls [i].hue = random () % 360;
240           hsv_to_rgb (balls [i].hue, 1.0, 1.0,
241                       &balls [i].color.red, &balls [i].color.green,
242                       &balls [i].color.blue);
243           if (!XAllocColor (dpy, cmap, &balls [i].color))
244             mono_p = True; /* just give up */
245         }
246     }
247
248   if (orbit_p)
249     {
250       double a = 0;
251       double v;
252       double v_mult = get_float_resource ("vMult", "Float");
253       if (v_mult == 0.0) v_mult = 1.0;
254
255       for (i = 1; i < npoints; i++)
256         {
257           double _2ipi_n = (2 * i * M_PI / npoints);
258           double x = r * cos (_2ipi_n);
259           double y = r * sin (_2ipi_n);
260           double distx = r - x;
261           double dist2 = (distx * distx) + (y * y);
262           double dist = sqrt (dist2);
263           double a1 = ((balls[i].mass / dist2) *
264                        ((dist < threshold) ? -1.0 : 1.0) *
265                        (distx / dist));
266           a += a1;
267         }
268       if (a < 0.0)
269         {
270           fprintf (stderr, "%s: domain error: forces on balls too great\n",
271                    progname);
272           exit (-1);
273         }
274       v = sqrt (a * r) * v_mult;
275       for (i = 0; i < npoints; i++)
276         {
277           double k = ((2 * i * M_PI / npoints) + th);
278           balls [i].vx = -v * sin (k);
279           balls [i].vy =  v * cos (k);
280         }
281     }
282
283   if (mono_p) glow_p = False;
284   XClearWindow (dpy, window);
285 }
286
287 static void
288 compute_force (i, dx_ret, dy_ret)
289      int i;
290      float *dx_ret, *dy_ret;
291 {
292   int j;
293   float x_dist, y_dist, dist, dist2;
294   *dx_ret = 0;
295   *dy_ret = 0;
296   for (j = 0; j < npoints; j++)
297     {
298       float x_dist, y_dist, dist, dist2;
299
300       if (i == j) continue;
301       x_dist = balls [j].x - balls [i].x;
302       y_dist = balls [j].y - balls [i].y;
303       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
304       dist = sqrt (dist2);
305               
306       if (dist > 0.1) /* the balls are not overlapping */
307         {
308           float new_acc = ((balls[j].mass / dist2) *
309                            ((dist < threshold) ? -1.0 : 1.0));
310           float new_acc_dist = new_acc / dist;
311           *dx_ret += new_acc_dist * x_dist;
312           *dy_ret += new_acc_dist * y_dist;
313         }
314       else
315         {               /* the balls are overlapping; move randomly */
316           *dx_ret += (frand (10.0) - 5.0);
317           *dy_ret += (frand (10.0) - 5.0);
318         }
319     }
320
321   if (mouse_p)
322     {
323       x_dist = mouse_x - balls [i].x;
324       y_dist = mouse_y - balls [i].y;
325       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
326       dist = sqrt (dist2);
327         
328       if (dist > 0.1) /* the balls are not overlapping */
329         {
330           float new_acc = ((mouse_mass / dist2) *
331                            ((dist < threshold) ? -1.0 : 1.0));
332           float new_acc_dist = new_acc / dist;
333           *dx_ret += new_acc_dist * x_dist;
334           *dy_ret += new_acc_dist * y_dist;
335         }
336       else
337         {               /* the balls are overlapping; move randomly */
338           *dx_ret += (frand (10.0) - 5.0);
339           *dy_ret += (frand (10.0) - 5.0);
340         }
341     }
342 }
343
344 static void
345 run_balls (dpy, window)
346      Display *dpy;
347      Window window;
348 {
349   int last_point_stack_fp = point_stack_fp;
350   static int tick = 500, xlim, ylim;
351   static Colormap cmap;
352   int i;
353
354   /*flip mods for mouse interaction*/
355   Window  root1, child1;
356   int mask;
357   if (mouse_p)
358     {
359       XQueryPointer(dpy, window, &root1, &child1,
360                     &root_x, &root_y, &mouse_x, &mouse_y, &mask);
361     }
362
363   if (tick++ == 500)
364     {
365       XWindowAttributes xgwa;
366       XGetWindowAttributes (dpy, window, &xgwa);
367       tick = 0;
368       xlim = xgwa.width;
369       ylim = xgwa.height;
370       cmap = xgwa.colormap;
371     }
372
373   /* compute the force of attraction/repulsion among all balls */
374   for (i = 0; i < npoints; i++)
375     compute_force (i, &balls[i].dx, &balls[i].dy);
376
377   /* move the balls according to the forces now in effect */
378   for (i = 0; i < npoints; i++)
379     {
380       float old_x = balls[i].x;
381       float old_y = balls[i].y;
382       float new_x, new_y;
383       int size = balls[i].size;
384       balls[i].vx += balls[i].dx;
385       balls[i].vy += balls[i].dy;
386
387       /* don't let them get too fast: impose a terminal velocity
388          (actually, make the medium have friction) */
389       if (balls[i].vx > 10)
390         {
391           balls[i].vx *= 0.9;
392           balls[i].dx = 0;
393         }
394       else if (viscosity != 1)
395         {
396           balls[i].vx *= viscosity;
397         }
398
399       if (balls[i].vy > 10)
400         {
401           balls[i].vy *= 0.9;
402           balls[i].dy = 0;
403         }
404       else if (viscosity != 1)
405         {
406           balls[i].vy *= viscosity;
407         }
408
409       balls[i].x += balls[i].vx;
410       balls[i].y += balls[i].vy;
411
412       /* bounce off the walls */
413       if (balls[i].x >= (xlim - balls[i].size))
414         {
415           balls[i].x = (xlim - balls[i].size - 1);
416           if (balls[i].vx > 0)
417             balls[i].vx = -balls[i].vx;
418         }
419       if (balls[i].y >= (ylim - balls[i].size))
420         {
421           balls[i].y = (ylim - balls[i].size - 1);
422           if (balls[i].vy > 0)
423             balls[i].vy = -balls[i].vy;
424         }
425       if (balls[i].x <= 0)
426         {
427           balls[i].x = 0;
428           if (balls[i].vx < 0)
429             balls[i].vx = -balls[i].vx;
430         }
431       if (balls[i].y <= 0)
432         {
433           balls[i].y = 0;
434           if (balls[i].vy < 0)
435             balls[i].vy = -balls[i].vy;
436         }
437
438       new_x = balls[i].x;
439       new_y = balls[i].y;
440
441       /* make color saturation be related to particle acceleration. */
442       if (glow_p)
443         {
444           float limit = 0.5;
445           double s, v, fraction;
446           float vx = balls [i].dx;
447           float vy = balls [i].dy;
448           XColor new_color;
449           if (vx < 0) vx = -vx;
450           if (vy < 0) vy = -vy;
451           fraction = vx + vy;
452           if (fraction > limit) fraction = limit;
453
454           s = 1 - (fraction / limit);
455           v = 1.0;
456
457           s = (s * 0.75) + 0.25;
458
459           hsv_to_rgb (balls [i].hue, s, v, 
460                       &new_color.red, &new_color.green, &new_color.blue);
461           if (XAllocColor (dpy, cmap, &new_color))
462             {
463               XFreeColors (dpy, cmap, &balls [i].color.pixel, 1, 0);
464               balls [i].color = new_color;
465             }
466         }
467
468       if (mode == ball_mode)
469         {
470           if (!mono_p)
471             XSetForeground (dpy, draw_gc, balls [i].color.pixel);
472           XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
473                     size, size, 0, 360*64);
474           XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
475                     size, size, 0, 360*64);
476         }
477       if (mode != ball_mode)
478         {
479           point_stack [point_stack_fp].x = new_x;
480           point_stack [point_stack_fp].y = new_y;
481           point_stack_fp++;
482         }
483     }
484
485   /* draw the lines or polygons after computing all points */
486   if (mode != ball_mode)
487     {
488       point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
489       point_stack [point_stack_fp].y = balls [0].y;
490       point_stack_fp++;
491       if (point_stack_fp == point_stack_size)
492         point_stack_fp = 0;
493       else if (point_stack_fp > point_stack_size) /* better be aligned */
494         abort ();
495       if (!mono_p)
496         {
497           XColor color2, desired;
498           color2 = balls [0].color;
499           switch (cmode)
500             {
501             case cycle_mode:
502               cycle_hue (&color2, color_shift);
503               break;
504             case random_mode:
505               color2.red =   random () % 65535;
506               color2.green = random () % 65535;
507               color2.blue =  random () % 65535;
508               break;
509             default:
510               abort ();
511             }
512               
513           desired = color2;
514           if (XAllocColor (dpy, cmap, &color2))
515             {
516               /* XAllocColor returns the actual RGB that the hardware let us
517                  allocate.  Restore the requested values into the XColor struct
518                  so that limited-resolution hardware doesn't cause cycle_hue to
519                  get "stuck". */
520               color2.red = desired.red;
521               color2.green = desired.green;
522               color2.blue = desired.blue;
523             }
524           else
525             {
526               color2 = balls [0].color;
527               if (!XAllocColor (dpy, cmap, &balls [0].color))
528                 abort ();
529             }
530           pixel_stack [pixel_stack_fp++] = balls [0].color.pixel;
531           if (pixel_stack_fp >= pixel_stack_size)
532             pixel_stack_fp = 0;
533           XFreeColors (dpy, cmap, pixel_stack + pixel_stack_fp, 1, 0);
534           balls [0].color = color2;
535           XSetForeground (dpy, draw_gc, balls [0].color.pixel);
536         }
537     }
538
539   switch (mode)
540     {
541     case ball_mode:
542       break;
543     case line_mode:
544       if (segments > 0)
545         XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
546                     npoints + 1, CoordModeOrigin);
547       XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
548                   npoints + 1, CoordModeOrigin);
549       break;
550     case polygon_mode:
551       if (segments > 0)
552         XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
553                       npoints + 1, (npoints == 3 ? Convex : Complex),
554                       CoordModeOrigin);
555       XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
556                     npoints + 1, (npoints == 3 ? Convex : Complex),
557                     CoordModeOrigin);
558       break;
559     case tail_mode:
560       {
561         int i;
562         for (i = 0; i < npoints; i++)
563           {
564             int index = point_stack_fp + i;
565             int next_index = (index + (npoints + 1)) % point_stack_size;
566             XDrawLine (dpy, window, erase_gc,
567                        point_stack [index].x,
568                        point_stack [index].y,
569                        point_stack [next_index].x,
570                        point_stack [next_index].y);
571
572             index = last_point_stack_fp + i;
573             next_index = (index - (npoints + 1)) % point_stack_size;
574             if (next_index < 0) next_index += point_stack_size;
575             if (point_stack [next_index].x == 0 &&
576                 point_stack [next_index].y == 0)
577               continue;
578             XDrawLine (dpy, window, draw_gc,
579                        point_stack [index].x,
580                        point_stack [index].y,
581                        point_stack [next_index].x,
582                        point_stack [next_index].y);
583           }
584       }
585       break;
586     case spline_mode:
587     case spline_filled_mode:
588       {
589         int i;
590         static spline *s = 0;
591         if (! s) s = make_spline (npoints);
592         if (segments > 0)
593           {
594             for (i = 0; i < npoints; i++)
595               {
596                 s->control_x [i] = point_stack [point_stack_fp + i].x;
597                 s->control_y [i] = point_stack [point_stack_fp + i].y;
598               }
599             compute_closed_spline (s);
600             if (mode == spline_filled_mode)
601               XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
602                             (s->n_points == 3 ? Convex : Complex),
603                             CoordModeOrigin);
604             else
605               XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
606                           CoordModeOrigin);
607           }
608         for (i = 0; i < npoints; i++)
609           {
610             s->control_x [i] = point_stack [last_point_stack_fp + i].x;
611             s->control_y [i] = point_stack [last_point_stack_fp + i].y;
612           }
613         compute_closed_spline (s);
614         if (mode == spline_filled_mode)
615           XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
616                         (s->n_points == 3 ? Convex : Complex),
617                         CoordModeOrigin);
618         else
619           XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
620                       CoordModeOrigin);
621       }
622       break;
623     default:
624       abort ();
625     }
626
627   XSync (dpy, True);
628 }
629
630 \f
631 char *progclass = "Attraction";
632
633 char *defaults [] = {
634   "Attraction.background:       black",         /* to placate SGI */
635   "Attraction.foreground:       white",
636   "*mode:       balls",
637   "*points:     0",
638   "*size:       0",
639   "*threshold:  100",
640   "*delay:      10000",
641   "*glow:       false",
642   "*mouseSize:  10",
643   "*mouse:      false",
644   "*viscosity:  1",
645   "*orbit:      false",
646   "*colorShift: 3",
647   "*segments:   100",
648   0
649 };
650
651 XrmOptionDescRec options [] = {
652   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
653   { "-points",          ".points",      XrmoptionSepArg, 0 },
654   { "-threshold",       ".threshold",   XrmoptionSepArg, 0 },
655   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
656   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
657   { "-size",            ".size",        XrmoptionSepArg, 0 },
658   { "-color-mode",      ".colorMode",   XrmoptionSepArg, 0 },
659   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
660   { "-radius",          ".radius",      XrmoptionSepArg, 0 },
661   { "-vx",              ".vx",          XrmoptionSepArg, 0 },
662   { "-vy",              ".vy",          XrmoptionSepArg, 0 },
663   { "-vmult",           ".vMult",       XrmoptionSepArg, 0 },
664   { "-mouse-size",      ".mouseSize",   XrmoptionSepArg, 0 },
665   { "-mouse",           ".mouse",       XrmoptionNoArg, "true" },
666   { "-nomouse",         ".mouse",       XrmoptionNoArg, "false" },
667   { "-viscosity",       ".viscosity",   XrmoptionSepArg, 0 },
668   { "-glow",            ".glow",        XrmoptionNoArg, "true" },
669   { "-noglow",          ".glow",        XrmoptionNoArg, "false" },
670   { "-orbit",           ".orbit",       XrmoptionNoArg, "true" }
671 };
672 int options_size = (sizeof (options) / sizeof (options[0]));
673
674 void
675 screenhack (dpy, window)
676      Display *dpy;
677      Window window;
678 {
679   init_balls (dpy, window);
680   while (1)
681     {
682       run_balls (dpy, window);
683       if (delay) usleep (delay);
684     }
685 }