ftp://updates.redhat.com/enterprise/2.1AS/en/os/SRPMS/xscreensaver-3.33-4.rhel21...
[xscreensaver] / hacks / attraction.c
1 /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998, 2001
2  *  Jamie Zawinski <jwz@jwz.org>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 /* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
14    a little like the strong and weak electromagnetic forces.  Derived from
15    a Lispm screensaver by John Pezaris <pz@mit.edu>.  Mouse control and
16    viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.
17
18    John sez:
19
20        The simulation started out as a purely accurate gravitational
21        simulation, but, with constant simulation step size, I quickly
22        realized the field being simulated while grossly gravitational
23        was, in fact, non-conservative.  It also had the rather annoying
24        behavior of dealing very badly with colliding orbs.  Therefore,
25        I implemented a negative-gravity region (with two thresholds; as
26        I read your code, you only implemented one) to prevent orbs from
27        every coming too close together, and added a viscosity factor if
28        the speed of any orb got too fast.  This provides a nice stable
29        system with interesting behavior.
30
31        I had experimented with a number of fields including the van der
32        Waals force (very interesting orbiting behavior) and 1/r^3
33        gravity (not as interesting as 1/r^2).  An even normal viscosity
34        (rather than the thresholded version to bleed excess energy) is
35        also not interesting.  The 1/r^2, -1/r^2, -10/r^2 thresholds
36        proved not only robust but also interesting -- the orbs never
37        collided and the threshold viscosity fixed the
38        non-conservational problem.
39
40    Philip sez:
41        > An even normal viscosity (rather than the thresholded version to
42        > bleed excess energy) is also not interesting.
43
44        unless you make about 200 points.... set the viscosity to about .8
45        and drag the mouse through it.   it makes a nice wave which travels
46        through the field.
47
48    And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
49
50        Despite what John sez, the field being simulated is always
51        conservative.  The real problem is that it uses a simple hack,
52        computing acceleration *based only on the starting position*,
53        instead of a real differential equation solver.  Thus you'll
54        always have energy coming out of nowhere, although it's most
55        blatant when balls get close together.  If it were done right,
56        you wouldn't need viscosity or artificial limits on how close
57        the balls can get.
58
59    Matt <straitm@carleton.edu> sez:
60
61        Added a switch to remove the walls.
62
63        Added a switch to make the threshold viscosity optional.  If
64        nomaxspeed is specified, then balls going really fast do not
65        recieve special treatment.
66
67        I've made tail mode prettier by eliminating the first erase line
68        that drew from the upper left corner to the starting position of
69        each point.
70
71        Made the balls in modes other than "balls" bounce exactly at the
72        walls.  (Because the graphics for different modes are drawn
73        differently with respect to the "actual" position of the point,
74        they used to be able to run somewhat past the walls, or bounce
75        before hitting them.)
76
77        Added an option to output each ball's speed in the form of a bar
78        graph drawn on the same window as the balls.  If only x or y is
79        selected, they will be represented on the appropriate axis down
80        the center of the window.  If both are selected, they will both
81        be displayed along the diagonal such that the x and y bars for
82        each point start at the same place.  If speed is selected, the
83        speed will be displayed down the left side.  */
84
85 #include <stdio.h>
86 #include <math.h>
87 #include "screenhack.h"
88 #include "spline.h"
89
90 struct ball {
91   double x, y;
92   double vx, vy;
93   double dx, dy;
94   double mass;
95   int size;
96   int pixel_index;
97   int hue;
98 };
99
100 static struct ball *balls;
101 static double *x_vels;
102 static double *y_vels;
103 static double *speeds;
104 static int npoints;
105 static int threshold;
106 static int delay;
107 static int global_size;
108 static int segments;
109 static Bool glow_p;
110 static Bool orbit_p;
111 static Bool walls_p;
112 static Bool maxspeed_p;
113 static Bool cbounce_p;
114 static XPoint *point_stack;
115 static int point_stack_size, point_stack_fp;
116 static XColor *colors;
117 static int ncolors;
118 static int fg_index;
119 static int color_shift;
120 Bool no_erase_yet; /* for tail mode fix */
121
122 /*flip mods for mouse interaction*/
123 static Bool mouse_p;
124 int mouse_x, mouse_y, mouse_mass, root_x, root_y;
125 static double viscosity;
126
127 static enum object_mode {
128   ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
129   tail_mode
130 } mode;
131
132 static enum graph_mode {
133   graph_none, graph_x, graph_y, graph_both, graph_speed
134 } graph_mode;
135
136 static GC draw_gc, erase_gc;
137
138 /* The normal (and max) width for a graph bar */
139 #define BAR_SIZE 11
140 #define MAX_SIZE 16
141 #define min(a,b) ((a)<(b)?(a):(b))
142 #define max(a,b) ((a)>(b)?(a):(b))
143
144 static void
145 init_balls (Display *dpy, Window window)
146 {
147   int i;
148   XWindowAttributes xgwa;
149   XGCValues gcv;
150   int xlim, ylim, midx, midy, r, vx, vy;
151   double th;
152   Colormap cmap;
153   char *mode_str, *graph_mode_str;
154
155   XGetWindowAttributes (dpy, window, &xgwa);
156   xlim = xgwa.width;
157   ylim = xgwa.height;
158   cmap = xgwa.colormap;
159   midx = xlim/2;
160   midy = ylim/2;
161   walls_p = get_boolean_resource ("walls", "Boolean");
162
163   /* if there aren't walls, don't set a limit on the radius */
164   r = get_integer_resource ("radius", "Integer");
165   if (r <= 0 || (r > min (xlim/2, ylim/2) && walls_p) )
166     r = min (xlim/2, ylim/2) - 50;
167
168   vx = get_integer_resource ("vx", "Integer");
169   vy = get_integer_resource ("vy", "Integer");
170
171   npoints = get_integer_resource ("points", "Integer");
172   if (npoints < 1)
173     npoints = 3 + (random () % 5);
174   balls = (struct ball *) malloc (npoints * sizeof (struct ball));
175
176   no_erase_yet = 1; /* for tail mode fix */
177
178   segments = get_integer_resource ("segments", "Integer");
179   if (segments < 0) segments = 1;
180
181   threshold = get_integer_resource ("threshold", "Integer");
182   if (threshold < 0) threshold = 0;
183
184   delay = get_integer_resource ("delay", "Integer");
185   if (delay < 0) delay = 0;
186
187   global_size = get_integer_resource ("size", "Integer");
188   if (global_size < 0) global_size = 0;
189
190   glow_p = get_boolean_resource ("glow", "Boolean");
191
192   orbit_p = get_boolean_resource ("orbit", "Boolean");
193
194   maxspeed_p = get_boolean_resource ("maxspeed", "Boolean");
195
196   cbounce_p = get_boolean_resource ("cbounce", "Boolean");
197
198   color_shift = get_integer_resource ("colorShift", "Integer");
199   if (color_shift <= 0) color_shift = 5;
200
201   /*flip mods for mouse interaction*/
202   mouse_p = get_boolean_resource ("mouse", "Boolean");
203   mouse_mass = get_integer_resource ("mouseSize", "Integer");
204   mouse_mass =  mouse_mass *  mouse_mass *10;
205
206   viscosity = get_float_resource ("viscosity", "Float");
207
208   mode_str = get_string_resource ("mode", "Mode");
209   if (! mode_str) mode = ball_mode;
210   else if (!strcmp (mode_str, "balls"))         mode = ball_mode;
211   else if (!strcmp (mode_str, "lines"))         mode = line_mode;
212   else if (!strcmp (mode_str, "polygons"))      mode = polygon_mode;
213   else if (!strcmp (mode_str, "tails"))         mode = tail_mode;
214   else if (!strcmp (mode_str, "splines"))       mode = spline_mode;
215   else if (!strcmp (mode_str, "filled-splines"))mode = spline_filled_mode;
216   else {
217     fprintf (stderr,
218              "%s: mode must be balls, lines, tails, polygons, splines, or\n\
219         filled-splines, not \"%s\"\n",
220              progname, mode_str);
221     exit (1);
222   }
223
224   graph_mode_str = get_string_resource ("graphmode", "Mode");
225   if (! graph_mode_str) graph_mode = graph_none;
226   else if (!strcmp (graph_mode_str, "x"))       graph_mode = graph_x;
227   else if (!strcmp (graph_mode_str, "y"))       graph_mode = graph_y;
228   else if (!strcmp (graph_mode_str, "both"))    graph_mode = graph_both;
229   else if (!strcmp (graph_mode_str, "speed"))   graph_mode = graph_speed;
230   else if (!strcmp (graph_mode_str, "none"))    graph_mode = graph_none;
231   else {
232     fprintf (stderr,
233          "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
234          progname, graph_mode_str);
235     exit (1);
236   }
237
238   /* only allocate memory if it is needed */
239   if(graph_mode != graph_none)
240   {
241     if(graph_mode == graph_x || graph_mode == graph_both)
242       x_vels = (double *) malloc (npoints * sizeof (double));
243     if(graph_mode == graph_y || graph_mode == graph_both)
244       y_vels = (double *) malloc (npoints * sizeof (double));
245     if(graph_mode == graph_speed)
246       speeds = (double *) malloc (npoints * sizeof (double));
247   }
248
249   if (mode != ball_mode && mode != tail_mode) glow_p = False;
250   
251   if (mode == polygon_mode && npoints < 3)
252     mode = line_mode;
253
254   ncolors = get_integer_resource ("colors", "Colors");
255   if (ncolors < 2) ncolors = 2;
256   if (ncolors <= 2) mono_p = True;
257   colors = 0;
258
259   if (!mono_p)
260     {
261       fg_index = 0;
262       switch (mode)
263         {
264         case ball_mode:
265           if (glow_p)
266             {
267               int H = random() % 360;
268               double S1 = 0.25;
269               double S2 = 1.00;
270               double V = frand(0.25) + 0.75;
271               colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
272               make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, colors, &ncolors,
273                                False, True, False);
274             }
275           else
276             {
277               ncolors = npoints;
278               colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
279               make_random_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
280                                     True, True, False, True);
281             }
282           break;
283         case line_mode:
284         case polygon_mode:
285         case spline_mode:
286         case spline_filled_mode:
287         case tail_mode:
288           colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
289           make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
290                                 True, False, True);
291           break;
292         default:
293           abort ();
294         }
295     }
296
297   if (!mono_p && ncolors <= 2)
298     {
299       if (colors) free (colors);
300       colors = 0;
301       mono_p = True;
302     }
303
304   if (mode != ball_mode)
305     {
306       int size = (segments ? segments : 1);
307       point_stack_size = size * (npoints + 1);
308       point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
309       point_stack_fp = 0;
310     }
311
312   gcv.line_width = (mode == tail_mode
313                     ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
314                     : 1);
315   gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
316
317   if (mono_p)
318     gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap);
319   else
320     gcv.foreground = colors[fg_index].pixel;
321   draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
322
323   gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap);
324   erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
325
326
327 #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
328
329   if (orbit_p && !global_size)
330     /* To orbit, all objects must be the same mass, or the math gets
331        really hairy... */
332     global_size = rand_size ();
333
334   th = frand (M_PI+M_PI);
335   for (i = 0; i < npoints; i++)
336     {
337       int new_size = (global_size ? global_size : rand_size ());
338       balls [i].dx = 0;
339       balls [i].dy = 0;
340       balls [i].size = new_size;
341       balls [i].mass = (new_size * new_size * 10);
342       balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
343       balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
344       if (! orbit_p)
345         {
346           balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
347           balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
348         }
349       if (mono_p || mode != ball_mode)
350         balls [i].pixel_index = -1;
351       else if (glow_p)
352         balls [i].pixel_index = 0;
353       else
354         balls [i].pixel_index = random() % ncolors;
355     }
356
357   /*  This lets modes where the points don't really have any size use the whole
358       window.  Otherwise, since the points still have a positive size
359       assigned to them, they will be bounced somewhat early.  Mass and size are
360       seperate, so this shouldn't cause problems.  It's a bit kludgy, tho.
361   */
362   if(mode == line_mode || mode == spline_mode || 
363      mode == spline_filled_mode || mode == polygon_mode)
364     {
365         for(i = 1; i < npoints; i++)
366           {
367                 balls[i].size = 0;
368           }
369      }
370     
371   if (orbit_p)
372     {
373       double a = 0;
374       double v;
375       double v_mult = get_float_resource ("vMult", "Float");
376       if (v_mult == 0.0) v_mult = 1.0;
377
378       for (i = 1; i < npoints; i++)
379         {
380           double _2ipi_n = (2 * i * M_PI / npoints);
381           double x = r * cos (_2ipi_n);
382           double y = r * sin (_2ipi_n);
383           double distx = r - x;
384           double dist2 = (distx * distx) + (y * y);
385           double dist = sqrt (dist2);
386           double a1 = ((balls[i].mass / dist2) *
387                        ((dist < threshold) ? -1.0 : 1.0) *
388                        (distx / dist));
389           a += a1;
390         }
391       if (a < 0.0)
392         {
393           fprintf (stderr, "%s: domain error: forces on balls too great\n",
394                    progname);
395           exit (-1);
396         }
397       v = sqrt (a * r) * v_mult;
398       for (i = 0; i < npoints; i++)
399         {
400           double k = ((2 * i * M_PI / npoints) + th);
401           balls [i].vx = -v * sin (k);
402           balls [i].vy =  v * cos (k);
403         }
404     }
405
406   if (mono_p) glow_p = False;
407
408   XClearWindow (dpy, window);
409 }
410
411 static void
412 compute_force (int i, double *dx_ret, double *dy_ret)
413 {
414   int j;
415   double x_dist, y_dist, dist, dist2;
416   *dx_ret = 0;
417   *dy_ret = 0;
418   for (j = 0; j < npoints; j++)
419     {
420       if (i == j) continue;
421       x_dist = balls [j].x - balls [i].x;
422       y_dist = balls [j].y - balls [i].y;
423       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
424       dist = sqrt (dist2);
425               
426       if (dist > 0.1) /* the balls are not overlapping */
427         {
428           double new_acc = ((balls[j].mass / dist2) *
429                             ((dist < threshold) ? -1.0 : 1.0));
430           double new_acc_dist = new_acc / dist;
431           *dx_ret += new_acc_dist * x_dist;
432           *dy_ret += new_acc_dist * y_dist;
433         }
434       else
435         {               /* the balls are overlapping; move randomly */
436           *dx_ret += (frand (10.0) - 5.0);
437           *dy_ret += (frand (10.0) - 5.0);
438         }
439     }
440
441   if (mouse_p)
442     {
443       x_dist = mouse_x - balls [i].x;
444       y_dist = mouse_y - balls [i].y;
445       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
446       dist = sqrt (dist2);
447         
448       if (dist > 0.1) /* the balls are not overlapping */
449         {
450           double new_acc = ((mouse_mass / dist2) *
451                             ((dist < threshold) ? -1.0 : 1.0));
452           double new_acc_dist = new_acc / dist;
453           *dx_ret += new_acc_dist * x_dist;
454           *dy_ret += new_acc_dist * y_dist;
455         }
456       else
457         {               /* the balls are overlapping; move randomly */
458           *dx_ret += (frand (10.0) - 5.0);
459           *dy_ret += (frand (10.0) - 5.0);
460         }
461     }
462 }
463
464
465 /* Draws meters along the diagonal for the x velocity */
466 static void 
467 draw_meter_x(Display *dpy, Window window, GC draw_gc,
468              struct ball *balls, int i, int alone) 
469 {
470   XWindowAttributes xgwa;
471   int x1,x2,y,w1,w2,h;
472   XGetWindowAttributes (dpy, window, &xgwa);
473
474   /* set the width of the bars to use */
475   if(xgwa.height < BAR_SIZE*npoints)
476     {
477       y = i*(xgwa.height/npoints);
478       h = (xgwa.height/npoints) - 2;
479     }
480   else
481     {
482       y = BAR_SIZE*i;
483       h = BAR_SIZE - 2;
484     }
485   
486   if(alone)
487     {
488       x1 = xgwa.width/2;
489       x2 = x1;
490     }
491   else
492     {
493       x1 = i*(h+2);
494       if(x1 < i) 
495         x1 = i;
496       x2 = x1;
497     }
498
499   if(y<1) y=i;  
500   if(h<1) h=1;
501
502   w1 = (int)(20*x_vels[i]);
503   w2 = (int)(20*balls[i].vx);
504   x_vels[i] = balls[i].vx; 
505
506   if (w1<0) {
507     w1=-w1;
508     x1=x1-w1;
509   }
510   if (w2<0) {
511     w2=-w2;
512     x2=x2-w2;
513   }
514   XDrawRectangle(dpy,window,erase_gc,x1+(h+2)/2,y,w1,h);
515   XDrawRectangle(dpy,window,draw_gc,x2+(h+2)/2,y,w2,h);
516 }
517
518 /* Draws meters along the diagonal for the y velocity.
519    Is there some way to make draw_meter_x and draw_meter_y 
520    one function instead of two without making them completely unreadable?
521 */
522 static void 
523 draw_meter_y (Display *dpy, Window window, GC draw_gc,
524               struct ball *balls, int i, int alone) 
525 {
526   XWindowAttributes xgwa;
527   int y1,y2,x,h1,h2,w;
528   XGetWindowAttributes (dpy, window, &xgwa);
529
530   if(xgwa.height < BAR_SIZE*npoints){  /*needs to be height still */
531     x = i*(xgwa.height/npoints);
532     w = (xgwa.height/npoints) - 2;
533   }
534   else{
535     x = BAR_SIZE*i;
536     w = BAR_SIZE - 2;
537   }
538
539   if(alone)
540     {
541       y1 = xgwa.height/2;
542       y2 = y1;
543     }
544   else
545     {
546       y1 = i*(w+2);
547       if(y1 < i)
548         y1 = i;
549       y2 = y1;
550     }
551
552   if(x < 1) x = i;  
553   if(w < 1) w = 1;
554
555   h1 = (int)(20*y_vels[i]);
556   h2 = (int)(20*balls[i].vy);
557   y_vels[i] = balls[i].vy; 
558
559   if (h1<0) {
560     h1=-h1;
561     y1=y1-h1;
562   }
563   if (h2<0) {
564     h2=-h2;
565     y2=y2-h2;
566   }
567   XDrawRectangle(dpy,window,erase_gc,x,y1+(w+2)/2,w,h1);
568   XDrawRectangle(dpy,window,draw_gc,x,y2+(w+2)/2,w,h2);
569 }
570
571
572 /* Draws meters of the total speed of the balls */
573 static void
574 draw_meter_speed (Display *dpy, Window window, GC draw_gc, 
575                   struct ball *balls, int i) 
576 {
577   XWindowAttributes xgwa;
578   int y,x1,x2,h,w1,w2;
579   XGetWindowAttributes (dpy, window, &xgwa);
580
581   if(xgwa.height < BAR_SIZE*npoints)
582     {
583       y = i*(xgwa.height/npoints);
584       h = (xgwa.height/npoints) - 2;
585     }
586   else{
587     y = BAR_SIZE*i;
588     h = BAR_SIZE - 2;
589   }
590
591   x1 = 0;
592   x2 = x1;
593
594   if(y < 1) y = i;  
595   if(h < 1) h = 1;
596
597   w1 = (int)(5*speeds[i]);
598   w2 = (int)(5*(balls[i].vy*balls[i].vy+balls[i].vx*balls[i].vx));
599   speeds[i] =    balls[i].vy*balls[i].vy+balls[i].vx*balls[i].vx;
600
601   XDrawRectangle(dpy,window,erase_gc,x1,y,w1,h);
602   XDrawRectangle(dpy,window,draw_gc, x2,y,w2,h);
603 }
604
605 static void
606 run_balls (Display *dpy, Window window, int total_ticks)
607 {
608   int last_point_stack_fp = point_stack_fp;
609   static int tick = 500, xlim, ylim;
610   static Colormap cmap;
611   
612   Window root1, child1;  /*flip mods for mouse interaction*/
613   unsigned int mask;
614
615   int i, radius = global_size/2;
616   if(global_size == 0)
617     radius = (MAX_SIZE / 3);
618
619   if(graph_mode != graph_none)
620     {
621       if(graph_mode == graph_both)
622         {
623           for(i = 0; i < npoints; i++)
624             {
625               draw_meter_x(dpy,window,draw_gc, balls, i, 0);
626               draw_meter_y(dpy,window,draw_gc, balls, i, 0);
627             }
628         }
629       else if(graph_mode == graph_x)
630         {
631           for(i = 0; i < npoints; i++)
632             {
633               draw_meter_x(dpy,window,draw_gc, balls, i, 1);
634             }
635         }
636       else if(graph_mode == graph_y)
637         {
638           for(i = 0; i < npoints; i++)
639             {
640               draw_meter_y(dpy,window,draw_gc, balls, i, 1);
641             }
642         }
643       else if(graph_mode == graph_speed)
644         {
645           for(i = 0; i < npoints; i++)
646             {
647               draw_meter_speed(dpy,window,draw_gc, balls, i);
648             }
649         }
650
651     }
652
653   if (mouse_p)
654     {
655       XQueryPointer(dpy, window, &root1, &child1,
656                     &root_x, &root_y, &mouse_x, &mouse_y, &mask);
657     }
658
659   if (tick++ == 500)
660     {
661       XWindowAttributes xgwa;
662       XGetWindowAttributes (dpy, window, &xgwa);
663       tick = 0;
664       xlim = xgwa.width;
665       ylim = xgwa.height;
666       cmap = xgwa.colormap;
667     }
668
669   /* compute the force of attraction/repulsion among all balls */
670   for (i = 0; i < npoints; i++)
671     compute_force (i, &balls[i].dx, &balls[i].dy);
672
673   /* move the balls according to the forces now in effect */
674   for (i = 0; i < npoints; i++)
675     {
676       double old_x = balls[i].x;
677       double old_y = balls[i].y;
678       double new_x, new_y;
679       int size = balls[i].size;
680       balls[i].vx += balls[i].dx;
681       balls[i].vy += balls[i].dy;
682
683       /* "don't let them get too fast: impose a terminal velocity
684          (actually, make the medium have friction)"
685          Well, what this first step really does is give the medium a 
686          viscosity of .9 for balls going over the speed limit.  Anyway, 
687          this is now optional
688       */
689       if (balls[i].vx > 10 && maxspeed_p)
690         {
691           balls[i].vx *= 0.9;
692           balls[i].dx = 0;
693         }
694       else if (viscosity != 1)
695         {
696           balls[i].vx *= viscosity;
697         }
698
699       if (balls[i].vy > 10 && maxspeed_p)
700         {
701           balls[i].vy *= 0.9;
702           balls[i].dy = 0;
703         }
704       else if (viscosity != 1)
705         {
706           balls[i].vy *= viscosity;
707         }
708
709       balls[i].x += balls[i].vx;
710       balls[i].y += balls[i].vy;
711
712
713       /* bounce off the walls if desired
714          note: a ball is actually its upper left corner */
715       if(walls_p)
716         {
717           if(cbounce_p)  /* with correct bouncing */
718             {
719               /* so long as it's out of range, keep bouncing */
720         
721               while( (balls[i].x >= (xlim - balls[i].size)) ||
722                      (balls[i].y >= (ylim - balls[i].size)) ||
723                      (balls[i].x <= 0) ||
724                      (balls[i].y <= 0) )
725                 {
726                   if (balls[i].x >= (xlim - balls[i].size))
727                     {
728                       balls[i].x = (2*(xlim - balls[i].size) - balls[i].x);
729                       balls[i].vx = -balls[i].vx;
730                     }
731                   if (balls[i].y >= (ylim - balls[i].size))
732                     {
733                       balls[i].y = (2*(ylim - balls[i].size) - balls[i].y);
734                       balls[i].vy = -balls[i].vy;
735                     }
736                   if (balls[i].x <= 0)
737                     {
738                       balls[i].x = -balls[i].x;
739                       balls[i].vx = -balls[i].vx;
740                     }
741                   if (balls[i].y <= 0)
742                     {
743                       balls[i].y = -balls[i].y;
744                       balls[i].vy = -balls[i].vy;
745                     }
746                 }
747             }
748           else  /* with old bouncing */
749             {
750               if (balls[i].x >= (xlim - balls[i].size))
751                 {
752                   balls[i].x = (xlim - balls[i].size - 1);
753                   if (balls[i].vx > 0) /* why is this check here? */
754                     balls[i].vx = -balls[i].vx;
755                 }
756               if (balls[i].y >= (ylim - balls[i].size))
757                 {
758                   balls[i].y = (ylim - balls[i].size - 1);
759                   if (balls[i].vy > 0)
760                     balls[i].vy = -balls[i].vy;
761                 }
762               if (balls[i].x <= 0)
763                 {
764                   balls[i].x = 0;
765                   if (balls[i].vx < 0)
766                     balls[i].vx = -balls[i].vx;
767                 }
768               if (balls[i].y <= 0)
769                 {
770                   balls[i].y = 0;
771                   if (balls[i].vy < 0)
772                     balls[i].vy = -balls[i].vy;
773                 }
774             }
775         }
776       new_x = balls[i].x;
777       new_y = balls[i].y;
778
779       if (!mono_p)
780         {
781           if (mode == ball_mode)
782             {
783               if (glow_p)
784                 {
785                   /* make color saturation be related to particle
786                      acceleration. */
787                   double limit = 0.5;
788                   double s, fraction;
789                   double vx = balls [i].dx;
790                   double vy = balls [i].dy;
791                   if (vx < 0) vx = -vx;
792                   if (vy < 0) vy = -vy;
793                   fraction = vx + vy;
794                   if (fraction > limit) fraction = limit;
795
796                   s = 1 - (fraction / limit);
797                   balls[i].pixel_index = (ncolors * s);
798                 }
799               XSetForeground (dpy, draw_gc,
800                               colors[balls[i].pixel_index].pixel);
801             }
802         }
803
804       if (mode == ball_mode)
805         {
806           XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
807                     size, size, 0, 360*64);
808           XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
809                     size, size, 0, 360*64);
810         }
811       else
812         {
813           point_stack [point_stack_fp].x = new_x;
814           point_stack [point_stack_fp].y = new_y;
815           point_stack_fp++;
816         }
817     }
818
819   /* draw the lines or polygons after computing all points */
820   if (mode != ball_mode)
821     {
822       point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
823       point_stack [point_stack_fp].y = balls [0].y;
824       point_stack_fp++;
825       if (point_stack_fp == point_stack_size)
826         point_stack_fp = 0;
827       else if (point_stack_fp > point_stack_size) /* better be aligned */
828         abort ();
829       if (!mono_p)
830         {
831           static int tick = 0;
832           if (tick++ == color_shift)
833             {
834               tick = 0;
835               fg_index = (fg_index + 1) % ncolors;
836               XSetForeground (dpy, draw_gc, colors[fg_index].pixel);
837             }
838         }
839     }
840
841   switch (mode)
842     {
843     case ball_mode:
844       break;
845     case line_mode:
846       if (segments > 0)
847         XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
848                     npoints + 1, CoordModeOrigin);
849       XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
850                   npoints + 1, CoordModeOrigin);
851       break;
852     case polygon_mode:
853       if (segments > 0)
854         XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
855                       npoints + 1, (npoints == 3 ? Convex : Complex),
856                       CoordModeOrigin);
857       XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
858                     npoints + 1, (npoints == 3 ? Convex : Complex),
859                     CoordModeOrigin);
860       break;
861     case tail_mode:
862       {
863         for (i = 0; i < npoints; i++)
864           {
865             int index = point_stack_fp + i;
866             int next_index = (index + (npoints + 1)) % point_stack_size;
867             if(no_erase_yet == 1)
868               {
869                 if(total_ticks >= segments)
870                   {
871                     no_erase_yet = 0;
872                     XDrawLine (dpy, window, erase_gc,
873                                point_stack [index].x + radius,
874                                point_stack [index].y + radius,
875                                point_stack [next_index].x + radius,
876                                point_stack [next_index].y + radius);
877                   }
878               }
879             else
880               {
881                 XDrawLine (dpy, window, erase_gc,
882                            point_stack [index].x + radius,
883                            point_stack [index].y + radius,
884                            point_stack [next_index].x + radius,
885                            point_stack [next_index].y + radius);
886               }
887             index = last_point_stack_fp + i;
888             next_index = (index - (npoints + 1)) % point_stack_size;
889             if (next_index < 0) next_index += point_stack_size;
890             if (point_stack [next_index].x == 0 &&
891                 point_stack [next_index].y == 0)
892               continue;
893             XDrawLine (dpy, window, draw_gc,
894                        point_stack [index].x + radius,
895                        point_stack [index].y + radius,
896                        point_stack [next_index].x + radius,
897                        point_stack [next_index].y + radius);
898           }
899       }
900       break;
901     case spline_mode:
902     case spline_filled_mode:
903       {
904         static spline *s = 0;
905         if (! s) s = make_spline (npoints);
906         if (segments > 0)
907           {
908             for (i = 0; i < npoints; i++)
909               {
910                 s->control_x [i] = point_stack [point_stack_fp + i].x;
911                 s->control_y [i] = point_stack [point_stack_fp + i].y;
912               }
913             compute_closed_spline (s);
914             if (mode == spline_filled_mode)
915               XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
916                             (s->n_points == 3 ? Convex : Complex),
917                             CoordModeOrigin);
918             else
919               XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
920                           CoordModeOrigin);
921           }
922         for (i = 0; i < npoints; i++)
923           {
924             s->control_x [i] = point_stack [last_point_stack_fp + i].x;
925             s->control_y [i] = point_stack [last_point_stack_fp + i].y;
926           }
927         compute_closed_spline (s);
928         if (mode == spline_filled_mode)
929           XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
930                         (s->n_points == 3 ? Convex : Complex),
931                         CoordModeOrigin);
932         else
933           XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
934                       CoordModeOrigin);
935       }
936       break;
937     default:
938       abort ();
939     }
940
941   XSync (dpy, False);
942 }
943
944 \f
945 char *progclass = "Attraction";
946
947 char *defaults [] = {
948   ".background: black",
949   ".foreground: white",
950   "*mode:       balls",
951   "*graphmode:  none",
952   "*points:     0",
953   "*size:       0",
954   "*colors:     200",
955   "*threshold:  100",
956   "*delay:      10000",
957   "*glow:       false",
958   "*mouseSize:  10",
959   "*walls:      true",
960   "*maxspeed:   true",
961   "*cbounce:    true",
962   "*mouse:      false",
963   "*viscosity:  1",
964   "*orbit:      false",
965   "*colorShift: 3",
966   "*segments:   500",
967   "*vMult:      0.9",
968   0
969 };
970
971 XrmOptionDescRec options [] = {
972   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
973   { "-graphmode",       ".graphmode",   XrmoptionSepArg, 0 },
974   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
975   { "-points",          ".points",      XrmoptionSepArg, 0 },
976   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
977   { "-threshold",       ".threshold",   XrmoptionSepArg, 0 },
978   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
979   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
980   { "-size",            ".size",        XrmoptionSepArg, 0 },
981   { "-radius",          ".radius",      XrmoptionSepArg, 0 },
982   { "-vx",              ".vx",          XrmoptionSepArg, 0 },
983   { "-vy",              ".vy",          XrmoptionSepArg, 0 },
984   { "-vmult",           ".vMult",       XrmoptionSepArg, 0 },
985   { "-mouse-size",      ".mouseSize",   XrmoptionSepArg, 0 },
986   { "-viscosity",       ".viscosity",   XrmoptionSepArg, 0 },
987   { "-mouse",           ".mouse",       XrmoptionNoArg, "true" },
988   { "-nomouse",         ".mouse",       XrmoptionNoArg, "false" },
989   { "-glow",            ".glow",        XrmoptionNoArg, "true" },
990   { "-noglow",          ".glow",        XrmoptionNoArg, "false" },
991   { "-orbit",           ".orbit",       XrmoptionNoArg, "true" },
992   { "-nowalls",         ".walls",       XrmoptionNoArg, "false" },
993   { "-walls",           ".walls",       XrmoptionNoArg, "true" },
994   { "-nomaxspeed",      ".maxspeed",    XrmoptionNoArg, "false" },
995   { "-maxspeed",        ".maxspeed",    XrmoptionNoArg, "true" },
996   { "-correct-bounce",  ".cbounce",     XrmoptionNoArg, "false" },
997   { "-fast-bounce",     ".cbounce",     XrmoptionNoArg, "true" },
998   { 0, 0, 0, 0 }
999 };
1000
1001 void
1002 screenhack (Display *dpy, Window window)
1003 {
1004   /* for tail mode fix */
1005   int total_ticks = 0;
1006
1007   init_balls (dpy, window);
1008   while (1)
1009     {
1010       total_ticks++;
1011       run_balls (dpy, window, total_ticks);
1012       screenhack_handle_events (dpy);
1013       if (delay)
1014         usleep (delay);
1015     }
1016 }