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