From http://www.jwz.org/xscreensaver/xscreensaver-5.22.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
166   XGetWindowAttributes (dpy, window, &xgwa);
167   st->xlim = xgwa.width;
168   st->ylim = xgwa.height;
169   cmap = xgwa.colormap;
170   midx = st->xlim/2;
171   midy = st->ylim/2;
172   st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
173
174   /* if there aren't walls, don't set a limit on the radius */
175   r = get_integer_resource (dpy, "radius", "Integer");
176   if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
177     r = min (st->xlim/2, st->ylim/2) - 50;
178
179   vx = get_integer_resource (dpy, "vx", "Integer");
180   vy = get_integer_resource (dpy, "vy", "Integer");
181
182   st->npoints = get_integer_resource (dpy, "points", "Integer");
183   if (st->npoints < 1)
184     st->npoints = 3 + (random () % 5);
185   st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
186
187   st->no_erase_yet = 1; /* for tail mode fix */
188
189   st->segments = get_integer_resource (dpy, "segments", "Integer");
190   if (st->segments < 0) st->segments = 1;
191
192   st->threshold = get_integer_resource (dpy, "threshold", "Integer");
193   if (st->threshold < 0) st->threshold = 0;
194
195   st->delay = get_integer_resource (dpy, "delay", "Integer");
196   if (st->delay < 0) st->delay = 0;
197
198   st->global_size = get_integer_resource (dpy, "size", "Integer");
199   if (st->global_size < 0) st->global_size = 0;
200
201   st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
202
203   st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
204
205   st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
206
207   st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
208
209   st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
210   if (st->color_shift <= 0) st->color_shift = 5;
211
212   st->viscosity = get_float_resource (dpy, "viscosity", "Float");
213
214   mode_str = get_string_resource (dpy, "mode", "Mode");
215   if (! mode_str) st->mode = ball_mode;
216   else if (!strcmp (mode_str, "balls"))         st->mode = ball_mode;
217   else if (!strcmp (mode_str, "lines"))         st->mode = line_mode;
218   else if (!strcmp (mode_str, "polygons"))      st->mode = polygon_mode;
219   else if (!strcmp (mode_str, "tails"))         st->mode = tail_mode;
220   else if (!strcmp (mode_str, "splines"))       st->mode = spline_mode;
221   else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
222   else {
223     fprintf (stderr,
224              "%s: mode must be balls, lines, tails, polygons, splines, or\n\
225         filled-splines, not \"%s\"\n",
226              progname, mode_str);
227     exit (1);
228   }
229
230   graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
231   if (! graph_mode_str) st->graph_mode = graph_none;
232   else if (!strcmp (graph_mode_str, "x"))       st->graph_mode = graph_x;
233   else if (!strcmp (graph_mode_str, "y"))       st->graph_mode = graph_y;
234   else if (!strcmp (graph_mode_str, "both"))    st->graph_mode = graph_both;
235   else if (!strcmp (graph_mode_str, "speed"))   st->graph_mode = graph_speed;
236   else if (!strcmp (graph_mode_str, "none"))    st->graph_mode = graph_none;
237   else {
238     fprintf (stderr,
239          "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
240          progname, graph_mode_str);
241     exit (1);
242   }
243
244   /* only allocate memory if it is needed */
245   if(st->graph_mode != graph_none)
246   {
247     if(st->graph_mode == graph_x || st->graph_mode == graph_both)
248       st->x_vels = (double *) malloc (st->npoints * sizeof (double));
249     if(st->graph_mode == graph_y || st->graph_mode == graph_both)
250       st->y_vels = (double *) malloc (st->npoints * sizeof (double));
251     if(st->graph_mode == graph_speed)
252       st->speeds = (double *) malloc (st->npoints * sizeof (double));
253   }
254
255   if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
256   
257   if (st->mode == polygon_mode && st->npoints < 3)
258     st->mode = line_mode;
259
260   st->ncolors = get_integer_resource (dpy, "colors", "Colors");
261   if (st->ncolors < 2) st->ncolors = 2;
262   if (st->ncolors <= 2) mono_p = True;
263   st->colors = 0;
264
265   if (!mono_p)
266     {
267       st->fg_index = 0;
268       switch (st->mode)
269         {
270         case ball_mode:
271           if (st->glow_p)
272             {
273               int H = random() % 360;
274               double S1 = 0.25;
275               double S2 = 1.00;
276               double V = frand(0.25) + 0.75;
277               st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
278               make_color_ramp (xgwa.screen, xgwa.visual, cmap,
279                                H, S1, V, H, S2, V, st->colors, &st->ncolors,
280                                False, True, False);
281             }
282           else
283             {
284               st->ncolors = st->npoints;
285               st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
286               make_random_colormap (xgwa.screen, xgwa.visual, cmap, 
287                                     st->colors, &st->ncolors,
288                                     True, True, False, True);
289             }
290           break;
291         case line_mode:
292         case polygon_mode:
293         case spline_mode:
294         case spline_filled_mode:
295         case tail_mode:
296           st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
297           make_smooth_colormap (xgwa.screen, xgwa.visual, cmap,
298                                 st->colors, &st->ncolors,
299                                 True, False, True);
300           break;
301         default:
302           abort ();
303         }
304     }
305
306   if (!mono_p && st->ncolors <= 2)
307     {
308       if (st->colors) free (st->colors);
309       st->colors = 0;
310       mono_p = True;
311     }
312
313   st->mouse_pixel =
314     get_pixel_resource (dpy, cmap, "mouseForeground", "MouseForeground");
315   st->mouse_ball = -1;
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
465
466 /* Draws meters along the diagonal for the x velocity */
467 static void 
468 draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone) 
469 {
470   XWindowAttributes xgwa;
471   int x1,x2,y,w1,w2,h;
472   XGetWindowAttributes (dpy, window, &xgwa);
473
474   /* set the width of the bars to use */
475   if(xgwa.height < BAR_SIZE*st->npoints)
476     {
477       y = i*(xgwa.height/st->npoints);
478       h = (xgwa.height/st->npoints) - 2;
479     }
480   else
481     {
482       y = BAR_SIZE*i;
483       h = BAR_SIZE - 2;
484     }
485   
486   if(alone)
487     {
488       x1 = xgwa.width/2;
489       x2 = x1;
490     }
491   else
492     {
493       x1 = i*(h+2);
494       if(x1 < i) 
495         x1 = i;
496       x2 = x1;
497     }
498
499   if(y<1) y=i;  
500   if(h<1) h=1;
501
502   w1 = (int)(20*st->x_vels[i]);
503   w2 = (int)(20*st->balls[i].vx);
504   st->x_vels[i] = st->balls[i].vx; 
505
506   if (w1<0) {
507     w1=-w1;
508     x1=x1-w1;
509   }
510   if (w2<0) {
511     w2=-w2;
512     x2=x2-w2;
513   }
514   XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
515   XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
516 }
517
518 /* Draws meters along the diagonal for the y velocity.
519    Is there some way to make draw_meter_x and draw_meter_y 
520    one function instead of two without making them completely unreadable?
521 */
522 static void 
523 draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone) 
524 {
525   XWindowAttributes xgwa;
526   int y1,y2,x,h1,h2,w;
527   XGetWindowAttributes (dpy, window, &xgwa);
528
529   if(xgwa.height < BAR_SIZE*st->npoints){  /*needs to be height still */
530     x = i*(xgwa.height/st->npoints);
531     w = (xgwa.height/st->npoints) - 2;
532   }
533   else{
534     x = BAR_SIZE*i;
535     w = BAR_SIZE - 2;
536   }
537
538   if(alone)
539     {
540       y1 = xgwa.height/2;
541       y2 = y1;
542     }
543   else
544     {
545       y1 = i*(w+2);
546       if(y1 < i)
547         y1 = i;
548       y2 = y1;
549     }
550
551   if(x < 1) x = i;  
552   if(w < 1) w = 1;
553
554   h1 = (int)(20*st->y_vels[i]);
555   h2 = (int)(20*st->balls[i].vy);
556   st->y_vels[i] = st->balls[i].vy; 
557
558   if (h1<0) {
559     h1=-h1;
560     y1=y1-h1;
561   }
562   if (h2<0) {
563     h2=-h2;
564     y2=y2-h2;
565   }
566   XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
567   XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
568 }
569
570
571 /* Draws meters of the total speed of the balls */
572 static void
573 draw_meter_speed (Display *dpy, Window window, struct state *st, int i) 
574 {
575   XWindowAttributes xgwa;
576   int y,x1,x2,h,w1,w2;
577   XGetWindowAttributes (dpy, window, &xgwa);
578
579   if(xgwa.height < BAR_SIZE*st->npoints)
580     {
581       y = i*(xgwa.height/st->npoints);
582       h = (xgwa.height/st->npoints) - 2;
583     }
584   else{
585     y = BAR_SIZE*i;
586     h = BAR_SIZE - 2;
587   }
588
589   x1 = 0;
590   x2 = x1;
591
592   if(y < 1) y = i;  
593   if(h < 1) h = 1;
594
595   w1 = (int)(5*st->speeds[i]);
596   w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
597   st->speeds[i] =    st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
598
599   XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
600   XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
601 }
602
603 /* Returns the position of the mouse relative to the root window.
604  */
605 static void
606 query_mouse (Display *dpy, Window win, int *x, int *y)
607 {
608   Window root1, child1;
609   int mouse_x, mouse_y, root_x, root_y;
610   unsigned int mask;
611   if (XQueryPointer (dpy, win, &root1, &child1,
612                      &root_x, &root_y, &mouse_x, &mouse_y, &mask))
613     {
614       *x = mouse_x;
615       *y = mouse_y;
616     }
617   else
618     {
619       *x = -9999;
620       *y = -9999;
621     }
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   int i, radius = st->global_size/2;
631
632   st->total_ticks++;
633
634   if(st->global_size == 0)
635     radius = (MAX_SIZE / 3);
636
637   if(st->graph_mode != graph_none)
638     {
639       if(st->graph_mode == graph_both)
640         {
641           for(i = 0; i < st->npoints; i++)
642             {
643               draw_meter_x(dpy, window, st, i, 0);
644               draw_meter_y(dpy, window, st, i, 0);
645             }
646         }
647       else if(st->graph_mode == graph_x)
648         {
649           for(i = 0; i < st->npoints; i++)
650             {
651               draw_meter_x(dpy, window, st, i, 1);
652             }
653         }
654       else if(st->graph_mode == graph_y)
655         {
656           for(i = 0; i < st->npoints; i++)
657             {
658               draw_meter_y(dpy, window, st, i, 1);
659             }
660         }
661       else if(st->graph_mode == graph_speed)
662         {
663           for(i = 0; i < st->npoints; i++)
664             {
665               draw_meter_speed(dpy, window, st, i);
666             }
667         }
668
669     }
670
671   /* compute the force of attraction/repulsion among all balls */
672   for (i = 0; i < st->npoints; i++)
673     compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
674
675   /* move the balls according to the forces now in effect */
676   for (i = 0; i < st->npoints; i++)
677     {
678       double old_x = st->balls[i].x;
679       double old_y = st->balls[i].y;
680       double new_x, new_y;
681       int size = st->balls[i].size;
682
683       st->balls[i].vx += st->balls[i].dx;
684       st->balls[i].vy += st->balls[i].dy;
685
686       /* "don't let them get too fast: impose a terminal velocity
687          (actually, make the medium have friction)"
688          Well, what this first step really does is give the medium a 
689          viscosity of .9 for balls going over the speed limit.  Anyway, 
690          this is now optional
691       */
692       if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
693         {
694           st->balls[i].vx *= 0.9;
695           st->balls[i].dx = 0;
696         }
697       if (st->viscosity != 1)
698         {
699           st->balls[i].vx *= st->viscosity;
700         }
701
702       if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
703         {
704           st->balls[i].vy *= 0.9;
705           st->balls[i].dy = 0;
706         }
707       if (st->viscosity != 1)
708         {
709           st->balls[i].vy *= st->viscosity;
710         }
711
712       st->balls[i].x += st->balls[i].vx;
713       st->balls[i].y += st->balls[i].vy;
714
715
716       /* bounce off the walls if desired
717          note: a ball is actually its upper left corner */
718       if(st->walls_p)
719         {
720           if(st->cbounce_p)  /* with correct bouncing */
721             {
722               /* so long as it's out of range, keep bouncing */
723               /* limit the maximum number to bounce to 4.*/
724               int bounce_allowed = 4;
725         
726               while( bounce_allowed && (
727                      (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
728                      (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
729                      (st->balls[i].x <= 0) ||
730                      (st->balls[i].y <= 0) )
731                      )
732                 {
733                   bounce_allowed--;
734                   if (st->balls[i].x >= (st->xlim - st->balls[i].size))
735                     {
736                       st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
737                       st->balls[i].vx = -st->balls[i].vx;
738                     }
739                   if (st->balls[i].y >= (st->ylim - st->balls[i].size))
740                     {
741                       st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
742                       st->balls[i].vy = -st->balls[i].vy;
743                     }
744                   if (st->balls[i].x <= 0)
745                     {
746                       st->balls[i].x = -st->balls[i].x;
747                       st->balls[i].vx = -st->balls[i].vx;
748                     }
749                   if (st->balls[i].y <= 0)
750                     {
751                       st->balls[i].y = -st->balls[i].y;
752                       st->balls[i].vy = -st->balls[i].vy;
753                     }
754                 }
755             }
756           else  /* with old bouncing */
757             {
758               if (st->balls[i].x >= (st->xlim - st->balls[i].size))
759                 {
760                   st->balls[i].x = (st->xlim - st->balls[i].size - 1);
761                   if (st->balls[i].vx > 0) /* why is this check here? */
762                     st->balls[i].vx = -st->balls[i].vx;
763                 }
764               if (st->balls[i].y >= (st->ylim - st->balls[i].size))
765                 {
766                   st->balls[i].y = (st->ylim - st->balls[i].size - 1);
767                   if (st->balls[i].vy > 0)
768                     st->balls[i].vy = -st->balls[i].vy;
769                 }
770               if (st->balls[i].x <= 0)
771                 {
772                   st->balls[i].x = 0;
773                   if (st->balls[i].vx < 0)
774                     st->balls[i].vx = -st->balls[i].vx;
775                 }
776               if (st->balls[i].y <= 0)
777                 {
778                   st->balls[i].y = 0;
779                   if (st->balls[i].vy < 0)
780                     st->balls[i].vy = -st->balls[i].vy;
781                 }
782             }
783         }
784
785       if (i == st->mouse_ball)
786         {
787           int x, y;
788           query_mouse (dpy, window, &x, &y);
789           if (st->mode == ball_mode)
790             {
791               x -= st->balls[i].size / 2;
792               y -= st->balls[i].size / 2;
793             }
794
795           st->balls[i].x = x;
796           st->balls[i].y = y;
797         }
798
799       new_x = st->balls[i].x;
800       new_y = st->balls[i].y;
801
802       if (!mono_p)
803         {
804           if (st->mode == ball_mode)
805             {
806               if (st->glow_p)
807                 {
808                   /* make color saturation be related to particle
809                      acceleration. */
810                   double limit = 0.5;
811                   double s, fraction;
812                   double vx = st->balls [i].dx;
813                   double vy = st->balls [i].dy;
814                   if (vx < 0) vx = -vx;
815                   if (vy < 0) vy = -vy;
816                   fraction = vx + vy;
817                   if (fraction > limit) fraction = limit;
818
819                   s = 1 - (fraction / limit);
820                   st->balls[i].pixel_index = (st->ncolors * s);
821                 }
822               XSetForeground (dpy, st->draw_gc,
823                               (i == st->mouse_ball
824                                ? st->mouse_pixel
825                                : st->colors[st->balls[i].pixel_index].pixel));
826             }
827         }
828
829       if (st->mode == ball_mode)
830         {
831           XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
832                     size, size, 0, 360*64);
833           XFillArc (dpy, window, st->draw_gc,  (int) new_x, (int) new_y,
834                     size, size, 0, 360*64);
835         }
836       else
837         {
838           st->point_stack [st->point_stack_fp].x = new_x;
839           st->point_stack [st->point_stack_fp].y = new_y;
840           st->point_stack_fp++;
841         }
842     }
843
844   /* draw the lines or polygons after computing all points */
845   if (st->mode != ball_mode)
846     {
847       st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
848       st->point_stack [st->point_stack_fp].y = st->balls [0].y;
849       st->point_stack_fp++;
850       if (st->point_stack_fp == st->point_stack_size)
851         st->point_stack_fp = 0;
852       else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
853         abort ();
854       if (!mono_p)
855         {
856           if (st->color_tick++ == st->color_shift)
857             {
858               st->color_tick = 0;
859               st->fg_index = (st->fg_index + 1) % st->ncolors;
860               XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
861             }
862         }
863     }
864
865   switch (st->mode)
866     {
867     case ball_mode:
868       break;
869     case line_mode:
870       if (st->segments > 0)
871         XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
872                     st->npoints + 1, CoordModeOrigin);
873       XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
874                   st->npoints + 1, CoordModeOrigin);
875       break;
876     case polygon_mode:
877       if (st->segments > 0)
878         XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
879                       st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
880                       CoordModeOrigin);
881       XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
882                     st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
883                     CoordModeOrigin);
884       break;
885     case tail_mode:
886       {
887         for (i = 0; i < st->npoints; i++)
888           {
889             int index = st->point_stack_fp + i;
890             int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
891             if(st->no_erase_yet == 1)
892               {
893                 if(st->total_ticks >= st->segments)
894                   {
895                     st->no_erase_yet = 0;
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               }
903             else
904               {
905                 XDrawLine (dpy, window, st->erase_gc,
906                            st->point_stack [index].x + radius,
907                            st->point_stack [index].y + radius,
908                            st->point_stack [next_index].x + radius,
909                            st->point_stack [next_index].y + radius);
910               }
911             index = last_point_stack_fp + i;
912             next_index = (index - (st->npoints + 1)) % st->point_stack_size;
913             if (next_index < 0) next_index += st->point_stack_size;
914             if (st->point_stack [next_index].x == 0 &&
915                 st->point_stack [next_index].y == 0)
916               continue;
917             XDrawLine (dpy, window, st->draw_gc,
918                        st->point_stack [index].x + radius,
919                        st->point_stack [index].y + radius,
920                        st->point_stack [next_index].x + radius,
921                        st->point_stack [next_index].y + radius);
922           }
923       }
924       break;
925     case spline_mode:
926     case spline_filled_mode:
927       {
928         if (! st->spl) st->spl = make_spline (st->npoints);
929         if (st->segments > 0)
930           {
931             for (i = 0; i < st->npoints; i++)
932               {
933                 st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
934                 st->spl->control_y [i] = st->point_stack [st->point_stack_fp + i].y;
935               }
936             compute_closed_spline (st->spl);
937             if (st->mode == spline_filled_mode)
938               XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
939                             (st->spl->n_points == 3 ? Convex : Complex),
940                             CoordModeOrigin);
941             else
942               XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
943                           CoordModeOrigin);
944           }
945         for (i = 0; i < st->npoints; i++)
946           {
947             st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
948             st->spl->control_y [i] = st->point_stack [last_point_stack_fp + i].y;
949           }
950         compute_closed_spline (st->spl);
951         if (st->mode == spline_filled_mode)
952           XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
953                         (st->spl->n_points == 3 ? Convex : Complex),
954                         CoordModeOrigin);
955         else
956           XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
957                       CoordModeOrigin);
958       }
959       break;
960     default:
961       abort ();
962     }
963
964   return st->delay;
965 }
966
967 static void
968 attraction_reshape (Display *dpy, Window window, void *closure, 
969                     unsigned int w, unsigned int h)
970 {
971   struct state *st = (struct state *) closure;
972   st->xlim = w;
973   st->ylim = h;
974 }
975
976 static Bool
977 attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
978 {
979   struct state *st = (struct state *) closure;
980
981   if (event->xany.type == ButtonPress)
982     {
983       int i;
984       if (st->mouse_ball != -1)  /* second down-click?  drop the ball. */
985         {
986           st->mouse_ball = -1;
987           return True;
988         }
989       else
990         {
991           /* When trying to pick up a ball, first look for a click directly
992              inside the ball; but if we don't find it, expand the radius
993              outward until we find something nearby.
994            */
995           int x = event->xbutton.x;
996           int y = event->xbutton.y;
997           float max = 10 * (st->global_size ? st->global_size : MAX_SIZE);
998           float step = max / 100;
999           float r2;
1000           for (r2 = step; r2 < max; r2 += step)
1001             {
1002               for (i = 0; i < st->npoints; i++)
1003                 {
1004                   float d = ((st->balls[i].x - x) * (st->balls[i].x - x) +
1005                              (st->balls[i].y - y) * (st->balls[i].y - y));
1006                   float r = st->balls[i].size;
1007                   if (r2 > r) r = r2;
1008                   if (d < r*r)
1009                     {
1010                       st->mouse_ball = i;
1011                       return True;
1012                     }
1013                 }
1014             }
1015         }
1016       return True;
1017     }
1018   else if (event->xany.type == ButtonRelease)   /* drop the ball */
1019     {
1020       st->mouse_ball = -1;
1021       return True;
1022     }
1023
1024
1025
1026   return False;
1027 }
1028
1029 static void
1030 attraction_free (Display *dpy, Window window, void *closure)
1031 {
1032   struct state *st = (struct state *) closure;
1033
1034   if (st->balls)        free (st->balls);
1035   if (st->x_vels)       free (st->x_vels);
1036   if (st->y_vels)       free (st->y_vels);
1037   if (st->speeds)       free (st->speeds);
1038   if (st->point_stack)  free (st->point_stack);
1039   if (st->colors)       free (st->colors);
1040   if (st->spl)          free_spline (st->spl);
1041
1042   free (st);
1043 }
1044
1045 \f
1046 static const char *attraction_defaults [] = {
1047   ".background: black",
1048   ".foreground: white",
1049   "*fpsSolid:   true",
1050   "*mode:       balls",
1051   "*graphmode:  none",
1052   "*points:     0",
1053   "*size:       0",
1054   "*colors:     200",
1055   "*threshold:  200",
1056   "*delay:      10000",
1057   "*glow:       false",
1058   "*walls:      true",
1059   "*maxspeed:   true",
1060   "*cbounce:    true",
1061   "*viscosity:  1.0",
1062   "*orbit:      false",
1063   "*colorShift: 3",
1064   "*segments:   500",
1065   "*vMult:      0.9",
1066   "*radius:     0",
1067   "*vx:         0",
1068   "*vy:         0",
1069   "*mouseForeground: white",
1070 #ifdef USE_IPHONE
1071   "*ignoreRotation: True",
1072 #endif
1073   0
1074 };
1075
1076 static XrmOptionDescRec attraction_options [] = {
1077   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
1078   { "-graphmode",       ".graphmode",   XrmoptionSepArg, 0 },
1079   { "-colors",          ".colors",      XrmoptionSepArg, 0 },
1080   { "-points",          ".points",      XrmoptionSepArg, 0 },
1081   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
1082   { "-threshold",       ".threshold",   XrmoptionSepArg, 0 },
1083   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
1084   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
1085   { "-size",            ".size",        XrmoptionSepArg, 0 },
1086   { "-radius",          ".radius",      XrmoptionSepArg, 0 },
1087   { "-vx",              ".vx",          XrmoptionSepArg, 0 },
1088   { "-vy",              ".vy",          XrmoptionSepArg, 0 },
1089   { "-vmult",           ".vMult",       XrmoptionSepArg, 0 },
1090   { "-viscosity",       ".viscosity",   XrmoptionSepArg, 0 },
1091   { "-glow",            ".glow",        XrmoptionNoArg, "true" },
1092   { "-noglow",          ".glow",        XrmoptionNoArg, "false" },
1093   { "-orbit",           ".orbit",       XrmoptionNoArg, "true" },
1094   { "-nowalls",         ".walls",       XrmoptionNoArg, "false" },
1095   { "-walls",           ".walls",       XrmoptionNoArg, "true" },
1096   { "-nomaxspeed",      ".maxspeed",    XrmoptionNoArg, "false" },
1097   { "-maxspeed",        ".maxspeed",    XrmoptionNoArg, "true" },
1098   { "-correct-bounce",  ".cbounce",     XrmoptionNoArg, "false" },
1099   { "-fast-bounce",     ".cbounce",     XrmoptionNoArg, "true" },
1100   { 0, 0, 0, 0 }
1101 };
1102
1103
1104 XSCREENSAVER_MODULE ("Attraction", attraction)