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