ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / hacks / attraction.c
1 /* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998, 2001, 2006
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 /* The normal (and max) width for a graph bar */
91 #define BAR_SIZE 11
92 #define MAX_SIZE 16
93 #define min(a,b) ((a)<(b)?(a):(b))
94 #define max(a,b) ((a)>(b)?(a):(b))
95
96
97 enum object_mode {
98   ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
99   tail_mode
100 };
101
102 enum graph_mode {
103   graph_none, graph_x, graph_y, graph_both, graph_speed
104 };
105
106 struct ball {
107   double x, y;
108   double vx, vy;
109   double dx, dy;
110   double mass;
111   int size;
112   int pixel_index;
113   int hue;
114 };
115
116 struct state {
117   struct ball *balls;
118   double *x_vels;
119   double *y_vels;
120   double *speeds;
121   int npoints;
122   int threshold;
123   int delay;
124   int global_size;
125   int segments;
126   Bool glow_p;
127   Bool orbit_p;
128   Bool walls_p;
129   Bool maxspeed_p;
130   Bool cbounce_p;
131   XPoint *point_stack;
132   int point_stack_size, point_stack_fp;
133   XColor *colors;
134   int ncolors;
135   int fg_index;
136   int color_shift;
137   int xlim, ylim;
138   Bool no_erase_yet; /* for tail mode fix */
139
140   /*flip mods for mouse interaction*/
141   Bool mouse_p;
142   int mouse_x, mouse_y, mouse_mass, root_x, root_y;
143   double viscosity;
144
145   enum object_mode mode;
146   enum graph_mode graph_mode;
147
148   GC draw_gc, erase_gc;
149
150   int total_ticks;
151   int color_tick;
152   spline *spl;
153 };
154
155
156 static void *
157 attraction_init (Display *dpy, Window window)
158 {
159   struct state *st = (struct state *) calloc (1, sizeof(*st));
160   int i;
161   XWindowAttributes xgwa;
162   XGCValues gcv;
163   int midx, midy, r, vx, vy;
164   double th;
165   Colormap cmap;
166   char *mode_str, *graph_mode_str;
167
168   XGetWindowAttributes (dpy, window, &xgwa);
169   st->xlim = xgwa.width;
170   st->ylim = xgwa.height;
171   cmap = xgwa.colormap;
172   midx = st->xlim/2;
173   midy = st->ylim/2;
174   st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
175
176   /* if there aren't walls, don't set a limit on the radius */
177   r = get_integer_resource (dpy, "radius", "Integer");
178   if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
179     r = min (st->xlim/2, st->ylim/2) - 50;
180
181   vx = get_integer_resource (dpy, "vx", "Integer");
182   vy = get_integer_resource (dpy, "vy", "Integer");
183
184   st->npoints = get_integer_resource (dpy, "points", "Integer");
185   if (st->npoints < 1)
186     st->npoints = 3 + (random () % 5);
187   st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
188
189   st->no_erase_yet = 1; /* for tail mode fix */
190
191   st->segments = get_integer_resource (dpy, "segments", "Integer");
192   if (st->segments < 0) st->segments = 1;
193
194   st->threshold = get_integer_resource (dpy, "threshold", "Integer");
195   if (st->threshold < 0) st->threshold = 0;
196
197   st->delay = get_integer_resource (dpy, "delay", "Integer");
198   if (st->delay < 0) st->delay = 0;
199
200   st->global_size = get_integer_resource (dpy, "size", "Integer");
201   if (st->global_size < 0) st->global_size = 0;
202
203   st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
204
205   st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
206
207   st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
208
209   st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
210
211   st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
212   if (st->color_shift <= 0) st->color_shift = 5;
213
214   /*flip mods for mouse interaction*/
215   st->mouse_p = get_boolean_resource (dpy, "mouse", "Boolean");
216   st->mouse_mass = get_integer_resource (dpy, "mouseSize", "Integer");
217   st->mouse_mass =  st->mouse_mass *  st->mouse_mass *10;
218
219   st->viscosity = get_float_resource (dpy, "viscosity", "Float");
220
221   mode_str = get_string_resource (dpy, "mode", "Mode");
222   if (! mode_str) st->mode = ball_mode;
223   else if (!strcmp (mode_str, "balls"))         st->mode = ball_mode;
224   else if (!strcmp (mode_str, "lines"))         st->mode = line_mode;
225   else if (!strcmp (mode_str, "polygons"))      st->mode = polygon_mode;
226   else if (!strcmp (mode_str, "tails"))         st->mode = tail_mode;
227   else if (!strcmp (mode_str, "splines"))       st->mode = spline_mode;
228   else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
229   else {
230     fprintf (stderr,
231              "%s: mode must be balls, lines, tails, polygons, splines, or\n\
232         filled-splines, not \"%s\"\n",
233              progname, mode_str);
234     exit (1);
235   }
236
237   graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
238   if (! graph_mode_str) st->graph_mode = graph_none;
239   else if (!strcmp (graph_mode_str, "x"))       st->graph_mode = graph_x;
240   else if (!strcmp (graph_mode_str, "y"))       st->graph_mode = graph_y;
241   else if (!strcmp (graph_mode_str, "both"))    st->graph_mode = graph_both;
242   else if (!strcmp (graph_mode_str, "speed"))   st->graph_mode = graph_speed;
243   else if (!strcmp (graph_mode_str, "none"))    st->graph_mode = graph_none;
244   else {
245     fprintf (stderr,
246          "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
247          progname, graph_mode_str);
248     exit (1);
249   }
250
251   /* only allocate memory if it is needed */
252   if(st->graph_mode != graph_none)
253   {
254     if(st->graph_mode == graph_x || st->graph_mode == graph_both)
255       st->x_vels = (double *) malloc (st->npoints * sizeof (double));
256     if(st->graph_mode == graph_y || st->graph_mode == graph_both)
257       st->y_vels = (double *) malloc (st->npoints * sizeof (double));
258     if(st->graph_mode == graph_speed)
259       st->speeds = (double *) malloc (st->npoints * sizeof (double));
260   }
261
262   if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
263   
264   if (st->mode == polygon_mode && st->npoints < 3)
265     st->mode = line_mode;
266
267   st->ncolors = get_integer_resource (dpy, "colors", "Colors");
268   if (st->ncolors < 2) st->ncolors = 2;
269   if (st->ncolors <= 2) mono_p = True;
270   st->colors = 0;
271
272   if (!mono_p)
273     {
274       st->fg_index = 0;
275       switch (st->mode)
276         {
277         case ball_mode:
278           if (st->glow_p)
279             {
280               int H = random() % 360;
281               double S1 = 0.25;
282               double S2 = 1.00;
283               double V = frand(0.25) + 0.75;
284               st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
285               make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, st->colors, &st->ncolors,
286                                False, True, False);
287             }
288           else
289             {
290               st->ncolors = st->npoints;
291               st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
292               make_random_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
293                                     True, True, False, True);
294             }
295           break;
296         case line_mode:
297         case polygon_mode:
298         case spline_mode:
299         case spline_filled_mode:
300         case tail_mode:
301           st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
302           make_smooth_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
303                                 True, False, True);
304           break;
305         default:
306           abort ();
307         }
308     }
309
310   if (!mono_p && st->ncolors <= 2)
311     {
312       if (st->colors) free (st->colors);
313       st->colors = 0;
314       mono_p = True;
315     }
316
317   if (st->mode != ball_mode)
318     {
319       int size = (st->segments ? st->segments : 1);
320       st->point_stack_size = size * (st->npoints + 1);
321       st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint));
322       st->point_stack_fp = 0;
323     }
324
325   gcv.line_width = (st->mode == tail_mode
326                     ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3))
327                     : 1);
328   gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt);
329
330   if (mono_p)
331     gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground");
332   else
333     gcv.foreground = st->colors[st->fg_index].pixel;
334   st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
335
336   gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background");
337   st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
338
339
340 #ifdef HAVE_COCOA
341   jwxyz_XSetAntiAliasing (dpy, st->draw_gc,  False);
342   jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
343 #endif
344
345   /* let's make the balls bigger by default */
346 #define rand_size() (3 * (8 + (random () % 7)))
347
348   if (st->orbit_p && !st->global_size)
349     /* To orbit, all objects must be the same mass, or the math gets
350        really hairy... */
351     st->global_size = rand_size ();
352
353  RETRY_NO_ORBIT:
354   th = frand (M_PI+M_PI);
355   for (i = 0; i < st->npoints; i++)
356     {
357       int new_size = (st->global_size ? st->global_size : rand_size ());
358       st->balls [i].dx = 0;
359       st->balls [i].dy = 0;
360       st->balls [i].size = new_size;
361       st->balls [i].mass = (new_size * new_size * 10);
362       st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th);
363       st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th);
364       if (! st->orbit_p)
365         {
366           st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
367           st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
368         }
369       if (mono_p || st->mode != ball_mode)
370         st->balls [i].pixel_index = -1;
371       else if (st->glow_p)
372         st->balls [i].pixel_index = 0;
373       else
374         st->balls [i].pixel_index = random() % st->ncolors;
375     }
376
377   /*  This lets modes where the points don't really have any size use the whole
378       window.  Otherwise, since the points still have a positive size
379       assigned to them, they will be bounced somewhat early.  Mass and size are
380       seperate, so this shouldn't cause problems.  It's a bit kludgy, tho.
381   */
382   if(st->mode == line_mode || st->mode == spline_mode || 
383      st->mode == spline_filled_mode || st->mode == polygon_mode)
384     {
385         for(i = 1; i < st->npoints; i++)
386           {
387                 st->balls[i].size = 0;
388           }
389      }
390     
391   if (st->orbit_p)
392     {
393       double a = 0;
394       double v;
395       double v_mult = get_float_resource (dpy, "vMult", "Float");
396       if (v_mult == 0.0) v_mult = 1.0;
397
398       for (i = 1; i < st->npoints; i++)
399         {
400           double _2ipi_n = (2 * i * M_PI / st->npoints);
401           double x = r * cos (_2ipi_n);
402           double y = r * sin (_2ipi_n);
403           double distx = r - x;
404           double dist2 = (distx * distx) + (y * y);
405           double dist = sqrt (dist2);
406           double a1 = ((st->balls[i].mass / dist2) *
407                        ((dist < st->threshold) ? -1.0 : 1.0) *
408                        (distx / dist));
409           a += a1;
410         }
411       if (a < 0.0)
412         {
413           /* "domain error: forces on balls too great" */
414           fprintf (stderr, "%s: window too small for these orbit settings.\n",
415                    progname);
416           st->orbit_p = False;
417           goto RETRY_NO_ORBIT;
418         }
419       v = sqrt (a * r) * v_mult;
420       for (i = 0; i < st->npoints; i++)
421         {
422           double k = ((2 * i * M_PI / st->npoints) + th);
423           st->balls [i].vx = -v * sin (k);
424           st->balls [i].vy =  v * cos (k);
425         }
426     }
427
428   if (mono_p) st->glow_p = False;
429
430   XClearWindow (dpy, window);
431   return st;
432 }
433
434 static void
435 compute_force (struct state *st, int i, double *dx_ret, double *dy_ret)
436 {
437   int j;
438   double x_dist, y_dist, dist, dist2;
439   *dx_ret = 0;
440   *dy_ret = 0;
441   for (j = 0; j < st->npoints; j++)
442     {
443       if (i == j) continue;
444       x_dist = st->balls [j].x - st->balls [i].x;
445       y_dist = st->balls [j].y - st->balls [i].y;
446       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
447       dist = sqrt (dist2);
448               
449       if (dist > 0.1) /* the balls are not overlapping */
450         {
451           double new_acc = ((st->balls[j].mass / dist2) *
452                             ((dist < st->threshold) ? -1.0 : 1.0));
453           double new_acc_dist = new_acc / dist;
454           *dx_ret += new_acc_dist * x_dist;
455           *dy_ret += new_acc_dist * y_dist;
456         }
457       else
458         {               /* the balls are overlapping; move randomly */
459           *dx_ret += (frand (10.0) - 5.0);
460           *dy_ret += (frand (10.0) - 5.0);
461         }
462     }
463
464   if (st->mouse_p)
465     {
466       x_dist = st->mouse_x - st->balls [i].x;
467       y_dist = st->mouse_y - st->balls [i].y;
468       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
469       dist = sqrt (dist2);
470         
471       if (dist > 0.1) /* the balls are not overlapping */
472         {
473           double new_acc = ((st->mouse_mass / dist2) *
474                             ((dist < st->threshold) ? -1.0 : 1.0));
475           double new_acc_dist = new_acc / dist;
476           *dx_ret += new_acc_dist * x_dist;
477           *dy_ret += new_acc_dist * y_dist;
478         }
479       else
480         {               /* the balls are overlapping; move randomly */
481           *dx_ret += (frand (10.0) - 5.0);
482           *dy_ret += (frand (10.0) - 5.0);
483         }
484     }
485 }
486
487
488 /* Draws meters along the diagonal for the x velocity */
489 static void 
490 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone) 
491 {
492   XWindowAttributes xgwa;
493   int x1,x2,y,w1,w2,h;
494   XGetWindowAttributes (dpy, window, &xgwa);
495
496   /* set the width of the bars to use */
497   if(xgwa.height < BAR_SIZE*st->npoints)
498     {
499       y = i*(xgwa.height/st->npoints);
500       h = (xgwa.height/st->npoints) - 2;
501     }
502   else
503     {
504       y = BAR_SIZE*i;
505       h = BAR_SIZE - 2;
506     }
507   
508   if(alone)
509     {
510       x1 = xgwa.width/2;
511       x2 = x1;
512     }
513   else
514     {
515       x1 = i*(h+2);
516       if(x1 < i) 
517         x1 = i;
518       x2 = x1;
519     }
520
521   if(y<1) y=i;  
522   if(h<1) h=1;
523
524   w1 = (int)(20*st->x_vels[i]);
525   w2 = (int)(20*st->balls[i].vx);
526   st->x_vels[i] = st->balls[i].vx; 
527
528   if (w1<0) {
529     w1=-w1;
530     x1=x1-w1;
531   }
532   if (w2<0) {
533     w2=-w2;
534     x2=x2-w2;
535   }
536   XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
537   XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
538 }
539
540 /* Draws meters along the diagonal for the y velocity.
541    Is there some way to make draw_meter_x and draw_meter_y 
542    one function instead of two without making them completely unreadable?
543 */
544 static void 
545 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone) 
546 {
547   XWindowAttributes xgwa;
548   int y1,y2,x,h1,h2,w;
549   XGetWindowAttributes (dpy, window, &xgwa);
550
551   if(xgwa.height < BAR_SIZE*st->npoints){  /*needs to be height still */
552     x = i*(xgwa.height/st->npoints);
553     w = (xgwa.height/st->npoints) - 2;
554   }
555   else{
556     x = BAR_SIZE*i;
557     w = BAR_SIZE - 2;
558   }
559
560   if(alone)
561     {
562       y1 = xgwa.height/2;
563       y2 = y1;
564     }
565   else
566     {
567       y1 = i*(w+2);
568       if(y1 < i)
569         y1 = i;
570       y2 = y1;
571     }
572
573   if(x < 1) x = i;  
574   if(w < 1) w = 1;
575
576   h1 = (int)(20*st->y_vels[i]);
577   h2 = (int)(20*st->balls[i].vy);
578   st->y_vels[i] = st->balls[i].vy; 
579
580   if (h1<0) {
581     h1=-h1;
582     y1=y1-h1;
583   }
584   if (h2<0) {
585     h2=-h2;
586     y2=y2-h2;
587   }
588   XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
589   XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
590 }
591
592
593 /* Draws meters of the total speed of the balls */
594 static void
595 draw_meter_speed (Display *dpy, Window window, struct state *st, int i) 
596 {
597   XWindowAttributes xgwa;
598   int y,x1,x2,h,w1,w2;
599   XGetWindowAttributes (dpy, window, &xgwa);
600
601   if(xgwa.height < BAR_SIZE*st->npoints)
602     {
603       y = i*(xgwa.height/st->npoints);
604       h = (xgwa.height/st->npoints) - 2;
605     }
606   else{
607     y = BAR_SIZE*i;
608     h = BAR_SIZE - 2;
609   }
610
611   x1 = 0;
612   x2 = x1;
613
614   if(y < 1) y = i;  
615   if(h < 1) h = 1;
616
617   w1 = (int)(5*st->speeds[i]);
618   w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
619   st->speeds[i] =    st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
620
621   XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
622   XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
623 }
624
625 static unsigned long
626 attraction_draw (Display *dpy, Window window, void *closure)
627 {
628   struct state *st = (struct state *) closure;
629   int last_point_stack_fp = st->point_stack_fp;
630   
631   Window root1, child1;  /*flip mods for mouse interaction*/
632   unsigned int mask;
633
634   int i, radius = st->global_size/2;
635
636   st->total_ticks++;
637
638   if(st->global_size == 0)
639     radius = (MAX_SIZE / 3);
640
641   if(st->graph_mode != graph_none)
642     {
643       if(st->graph_mode == graph_both)
644         {
645           for(i = 0; i < st->npoints; i++)
646             {
647               draw_meter_x(dpy, window, st, i, 0);
648               draw_meter_y(dpy, window, st, i, 0);
649             }
650         }
651       else if(st->graph_mode == graph_x)
652         {
653           for(i = 0; i < st->npoints; i++)
654             {
655               draw_meter_x(dpy, window, st, i, 1);
656             }
657         }
658       else if(st->graph_mode == graph_y)
659         {
660           for(i = 0; i < st->npoints; i++)
661             {
662               draw_meter_y(dpy, window, st, i, 1);
663             }
664         }
665       else if(st->graph_mode == graph_speed)
666         {
667           for(i = 0; i < st->npoints; i++)
668             {
669               draw_meter_speed(dpy, window, st, i);
670             }
671         }
672
673     }
674
675   if (st->mouse_p)
676     {
677       XQueryPointer(dpy, window, &root1, &child1,
678                     &st->root_x, &st->root_y, &st->mouse_x, &st->mouse_y, &mask);
679     }
680
681   /* compute the force of attraction/repulsion among all balls */
682   for (i = 0; i < st->npoints; i++)
683     compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
684
685   /* move the balls according to the forces now in effect */
686   for (i = 0; i < st->npoints; i++)
687     {
688       double old_x = st->balls[i].x;
689       double old_y = st->balls[i].y;
690       double new_x, new_y;
691       int size = st->balls[i].size;
692       st->balls[i].vx += st->balls[i].dx;
693       st->balls[i].vy += st->balls[i].dy;
694
695       /* "don't let them get too fast: impose a terminal velocity
696          (actually, make the medium have friction)"
697          Well, what this first step really does is give the medium a 
698          viscosity of .9 for balls going over the speed limit.  Anyway, 
699          this is now optional
700       */
701       if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
702         {
703           st->balls[i].vx *= 0.9;
704           st->balls[i].dx = 0;
705         }
706       if (st->viscosity != 1)
707         {
708           st->balls[i].vx *= st->viscosity;
709         }
710
711       if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
712         {
713           st->balls[i].vy *= 0.9;
714           st->balls[i].dy = 0;
715         }
716       if (st->viscosity != 1)
717         {
718           st->balls[i].vy *= st->viscosity;
719         }
720
721       st->balls[i].x += st->balls[i].vx;
722       st->balls[i].y += st->balls[i].vy;
723
724
725       /* bounce off the walls if desired
726          note: a ball is actually its upper left corner */
727       if(st->walls_p)
728         {
729           if(st->cbounce_p)  /* with correct bouncing */
730             {
731               /* so long as it's out of range, keep bouncing */
732               /* limit the maximum number to bounce to 4.*/
733               int bounce_allowed = 4;
734         
735               while( bounce_allowed && (
736                      (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
737                      (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
738                      (st->balls[i].x <= 0) ||
739                      (st->balls[i].y <= 0) )
740                      )
741                 {
742                   bounce_allowed--;
743                   if (st->balls[i].x >= (st->xlim - st->balls[i].size))
744                     {
745                       st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
746                       st->balls[i].vx = -st->balls[i].vx;
747                     }
748                   if (st->balls[i].y >= (st->ylim - st->balls[i].size))
749                     {
750                       st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
751                       st->balls[i].vy = -st->balls[i].vy;
752                     }
753                   if (st->balls[i].x <= 0)
754                     {
755                       st->balls[i].x = -st->balls[i].x;
756                       st->balls[i].vx = -st->balls[i].vx;
757                     }
758                   if (st->balls[i].y <= 0)
759                     {
760                       st->balls[i].y = -st->balls[i].y;
761                       st->balls[i].vy = -st->balls[i].vy;
762                     }
763                 }
764             }
765           else  /* with old bouncing */
766             {
767               if (st->balls[i].x >= (st->xlim - st->balls[i].size))
768                 {
769                   st->balls[i].x = (st->xlim - st->balls[i].size - 1);
770                   if (st->balls[i].vx > 0) /* why is this check here? */
771                     st->balls[i].vx = -st->balls[i].vx;
772                 }
773               if (st->balls[i].y >= (st->ylim - st->balls[i].size))
774                 {
775                   st->balls[i].y = (st->ylim - st->balls[i].size - 1);
776                   if (st->balls[i].vy > 0)
777                     st->balls[i].vy = -st->balls[i].vy;
778                 }
779               if (st->balls[i].x <= 0)
780                 {
781                   st->balls[i].x = 0;
782                   if (st->balls[i].vx < 0)
783                     st->balls[i].vx = -st->balls[i].vx;
784                 }
785               if (st->balls[i].y <= 0)
786                 {
787                   st->balls[i].y = 0;
788                   if (st->balls[i].vy < 0)
789                     st->balls[i].vy = -st->balls[i].vy;
790                 }
791             }
792         }
793       new_x = st->balls[i].x;
794       new_y = st->balls[i].y;
795
796       if (!mono_p)
797         {
798           if (st->mode == ball_mode)
799             {
800               if (st->glow_p)
801                 {
802                   /* make color saturation be related to particle
803                      acceleration. */
804                   double limit = 0.5;
805                   double s, fraction;
806                   double vx = st->balls [i].dx;
807                   double vy = st->balls [i].dy;
808                   if (vx < 0) vx = -vx;
809                   if (vy < 0) vy = -vy;
810                   fraction = vx + vy;
811                   if (fraction > limit) fraction = limit;
812
813                   s = 1 - (fraction / limit);
814                   st->balls[i].pixel_index = (st->ncolors * s);
815                 }
816               XSetForeground (dpy, st->draw_gc,
817                               st->colors[st->balls[i].pixel_index].pixel);
818             }
819         }
820
821       if (st->mode == ball_mode)
822         {
823           XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
824                     size, size, 0, 360*64);
825           XFillArc (dpy, window, st->draw_gc,  (int) new_x, (int) new_y,
826                     size, size, 0, 360*64);
827         }
828       else
829         {
830           st->point_stack [st->point_stack_fp].x = new_x;
831           st->point_stack [st->point_stack_fp].y = new_y;
832           st->point_stack_fp++;
833         }
834     }
835
836   /* draw the lines or polygons after computing all points */
837   if (st->mode != ball_mode)
838     {
839       st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
840       st->point_stack [st->point_stack_fp].y = st->balls [0].y;
841       st->point_stack_fp++;
842       if (st->point_stack_fp == st->point_stack_size)
843         st->point_stack_fp = 0;
844       else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
845         abort ();
846       if (!mono_p)
847         {
848           if (st->color_tick++ == st->color_shift)
849             {
850               st->color_tick = 0;
851               st->fg_index = (st->fg_index + 1) % st->ncolors;
852               XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
853             }
854         }
855     }
856
857   switch (st->mode)
858     {
859     case ball_mode:
860       break;
861     case line_mode:
862       if (st->segments > 0)
863         XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
864                     st->npoints + 1, CoordModeOrigin);
865       XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
866                   st->npoints + 1, CoordModeOrigin);
867       break;
868     case polygon_mode:
869       if (st->segments > 0)
870         XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
871                       st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
872                       CoordModeOrigin);
873       XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
874                     st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
875                     CoordModeOrigin);
876       break;
877     case tail_mode:
878       {
879         for (i = 0; i < st->npoints; i++)
880           {
881             int index = st->point_stack_fp + i;
882             int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
883             if(st->no_erase_yet == 1)
884               {
885                 if(st->total_ticks >= st->segments)
886                   {
887                     st->no_erase_yet = 0;
888                     XDrawLine (dpy, window, st->erase_gc,
889                                st->point_stack [index].x + radius,
890                                st->point_stack [index].y + radius,
891                                st->point_stack [next_index].x + radius,
892                                st->point_stack [next_index].y + radius);
893                   }
894               }
895             else
896               {
897                 XDrawLine (dpy, window, st->erase_gc,
898                            st->point_stack [index].x + radius,
899                            st->point_stack [index].y + radius,
900                            st->point_stack [next_index].x + radius,
901                            st->point_stack [next_index].y + radius);
902               }
903             index = last_point_stack_fp + i;
904             next_index = (index - (st->npoints + 1)) % st->point_stack_size;
905             if (next_index < 0) next_index += st->point_stack_size;
906             if (st->point_stack [next_index].x == 0 &&
907                 st->point_stack [next_index].y == 0)
908               continue;
909             XDrawLine (dpy, window, st->draw_gc,
910                        st->point_stack [index].x + radius,
911                        st->point_stack [index].y + radius,
912                        st->point_stack [next_index].x + radius,
913                        st->point_stack [next_index].y + radius);
914           }
915       }
916       break;
917     case spline_mode:
918     case spline_filled_mode:
919       {
920         if (! st->spl) st->spl = make_spline (st->npoints);
921         if (st->segments > 0)
922           {
923             for (i = 0; i < st->npoints; i++)
924               {
925                 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
926                 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
927               }
928             compute_closed_spline (st->spl);
929             if (st->mode == spline_filled_mode)
930               XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
931                             (st->spl->n_points == 3 ? Convex : Complex),
932                             CoordModeOrigin);
933             else
934               XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
935                           CoordModeOrigin);
936           }
937         for (i = 0; i < st->npoints; i++)
938           {
939             st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
940             st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
941           }
942         compute_closed_spline (st->spl);
943         if (st->mode == spline_filled_mode)
944           XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
945                         (st->spl->n_points == 3 ? Convex : Complex),
946                         CoordModeOrigin);
947         else
948           XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
949                       CoordModeOrigin);
950       }
951       break;
952     default:
953       abort ();
954     }
955
956   return st->delay;
957 }
958
959 static void
960 attraction_reshape (Display *dpy, Window window, void *closure, 
961                     unsigned int w, unsigned int h)
962 {
963   struct state *st = (struct state *) closure;
964   st->xlim = w;
965   st->ylim = h;
966 }
967
968 static Bool
969 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
970 {
971   return False;
972 }
973
974 static void
975 attraction_free (Display *dpy, Window window, void *closure)
976 {
977   struct state *st = (struct state *) closure;
978
979   if (st->balls)        free (st->balls);
980   if (st->x_vels)       free (st->x_vels);
981   if (st->y_vels)       free (st->y_vels);
982   if (st->speeds)       free (st->speeds);
983   if (st->point_stack)  free (st->point_stack);
984   if (st->colors)       free (st->colors);
985   if (st->spl)          free_spline (st->spl);
986
987   free (st);
988 }
989
990 \f
991 static const char *attraction_defaults [] = {
992   ".background: black",
993   ".foreground: white",
994   "*mode:       balls",
995   "*graphmode:  none",
996   "*points:     0",
997   "*size:       0",
998   "*colors:     200",
999   "*threshold:  200",
1000   "*delay:      10000",
1001   "*glow:       false",
1002   "*mouseSize:  10",
1003   "*walls:      true",
1004   "*maxspeed:   true",
1005   "*cbounce:    true",
1006   "*mouse:      false",
1007   "*viscosity:  1",
1008   "*orbit:      false",
1009   "*colorShift: 3",
1010   "*segments:   500",
1011   "*vMult:      0.9",
1012   "*radius:     0",
1013   "*vx:         0",
1014   "*vy:         0",
1015   0
1016 };
1017
1018 static XrmOptionDescRec attraction_options [] = {
1019   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
1020   { "-graphmode",       ".graphmode",   XrmoptionSepArg, 0 },
1021   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
1022   { "-points",          ".points",      XrmoptionSepArg, 0 },
1023   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
1024   { "-threshold",       ".threshold",   XrmoptionSepArg, 0 },
1025   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
1026   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
1027   { "-size",            ".size",        XrmoptionSepArg, 0 },
1028   { "-radius",          ".radius",      XrmoptionSepArg, 0 },
1029   { "-vx",              ".vx",          XrmoptionSepArg, 0 },
1030   { "-vy",              ".vy",          XrmoptionSepArg, 0 },
1031   { "-vmult",           ".vMult",       XrmoptionSepArg, 0 },
1032   { "-mouse-size",      ".mouseSize",   XrmoptionSepArg, 0 },
1033   { "-viscosity",       ".viscosity",   XrmoptionSepArg, 0 },
1034   { "-mouse",           ".mouse",       XrmoptionNoArg, "true" },
1035   { "-nomouse",         ".mouse",       XrmoptionNoArg, "false" },
1036   { "-glow",            ".glow",        XrmoptionNoArg, "true" },
1037   { "-noglow",          ".glow",        XrmoptionNoArg, "false" },
1038   { "-orbit",           ".orbit",       XrmoptionNoArg, "true" },
1039   { "-nowalls",         ".walls",       XrmoptionNoArg, "false" },
1040   { "-walls",           ".walls",       XrmoptionNoArg, "true" },
1041   { "-nomaxspeed",      ".maxspeed",    XrmoptionNoArg, "false" },
1042   { "-maxspeed",        ".maxspeed",    XrmoptionNoArg, "true" },
1043   { "-correct-bounce",  ".cbounce",     XrmoptionNoArg, "false" },
1044   { "-fast-bounce",     ".cbounce",     XrmoptionNoArg, "true" },
1045   { 0, 0, 0, 0 }
1046 };
1047
1048
1049 XSCREENSAVER_MODULE ("Attraction", attraction)