http://se.aminet.net/pub/X11/ftp.x.org/contrib/vms/xscreensaver-124.zip
[xscreensaver] / hacks / attraction.c
1 /* xscreensaver, Copyright (c) 1992 Jamie Zawinski <jwz@mcom.com>
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>.
15
16    John sez:
17
18    The simulation started out as a purely accurate gravitational simulation,
19    but, with constant simulation step size, I quickly realized the field being
20    simulated while grossly gravitational was, in fact, non-conservative.  It
21    also had the rather annoying behavior of dealing very badly with colliding
22    orbs.  Therefore, I implemented a negative-gravity region (with two
23    thresholds; as I read your code, you only implemented one) to prevent orbs
24    from every coming too close together, and added a viscosity factor if the
25    speed of any orb got too fast.  This provides a nice stable system with
26    interesting behavior.
27
28    I had experimented with a number of fields including the van der Waals
29    force (very interesting orbiting behavior) and 1/r^3 gravity (not as
30    interesting as 1/r^2).  An even normal viscosity (rather than the
31    thresholded version to bleed excess energy) is also not interesting.
32    The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
33    interesting -- the orbs never collided and the threshold viscosity fixed
34    the non-conservational problem.
35  */
36
37 #include "screenhack.h"
38 #include "spline.h"
39 #include <stdio.h>
40 #include <math.h>
41 #ifdef VMS
42 #define M_PI 3.14159265358979323846
43 #endif
44 #if __STDC__
45 #include <math.h>       /* for M_PI */
46 #endif
47
48 struct ball {
49   float x, y;
50   float vx, vy;
51   float dx, dy;
52   float mass;
53   int size;
54   XColor color;
55   int hue;
56 };
57
58 static unsigned int default_fg_pixel;
59 static struct ball *balls;
60 static int npoints;
61 static int threshold;
62 static int delay;
63 static int global_size;
64 static int segments;
65 static Bool glow_p;
66 static Bool orbit_p;
67 static XPoint *point_stack;
68 static int point_stack_size, point_stack_fp, pixel_stack_fp, pixel_stack_size;
69 static unsigned long *pixel_stack;
70 static unsigned int color_shift;
71
72 static enum object_mode {
73   ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
74   tail_mode
75 } mode;
76
77 static enum color_mode {
78   cycle_mode, random_mode
79 } cmode;
80
81 static GC draw_gc, erase_gc;
82
83 #define MAX_SIZE 16
84
85 #define min(a,b) ((a)<(b)?(a):(b))
86 #define max(a,b) ((a)>(b)?(a):(b))
87
88 static void
89 init_balls (dpy, window)
90      Display *dpy;
91      Window window;
92 {
93   int i;
94   XWindowAttributes xgwa;
95   XGCValues gcv;
96   int xlim, ylim, midx, midy, r, vx, vy;
97   double th;
98   Colormap cmap;
99   char *mode_str;
100   XGetWindowAttributes (dpy, window, &xgwa);
101   xlim = xgwa.width;
102   ylim = xgwa.height;
103   cmap = xgwa.colormap;
104   midx = xlim/2;
105   midy = ylim/2;
106   r = get_integer_resource ("radius", "Integer");
107   if (r <= 0 || r > min (xlim/2, ylim/2))
108     r = min (xlim/2, ylim/2) - 50;
109   vx = get_integer_resource ("vx", "Integer");
110   vy = get_integer_resource ("vy", "Integer");
111   npoints = get_integer_resource ("points", "Integer");
112   if (npoints < 1)
113     npoints = 3 + (random () % 5);
114   balls = (struct ball *) malloc (npoints * sizeof (struct ball));
115   segments = get_integer_resource ("segments", "Integer");
116   if (segments < 0) segments = 1;
117   threshold = get_integer_resource ("threshold", "Integer");
118   if (threshold < 0) threshold = 0;
119   delay = get_integer_resource ("delay", "Integer");
120     if (delay < 0) delay = 0;
121   global_size = get_integer_resource ("size", "Integer");
122   if (global_size < 0) global_size = 0;
123   glow_p = get_boolean_resource ("glow", "Boolean");
124   orbit_p = get_boolean_resource ("orbit", "Boolean");
125   color_shift = get_integer_resource ("colorShift", "Integer");
126   if (color_shift >= 360) color_shift = 5;
127
128   mode_str = get_string_resource ("mode", "Mode");
129   if (! mode_str) mode = ball_mode;
130   else if (!strcmp (mode_str, "balls")) mode = ball_mode;
131   else if (!strcmp (mode_str, "lines")) mode = line_mode;
132   else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
133   else if (!strcmp (mode_str, "tails")) mode = tail_mode;
134   else if (!strcmp (mode_str, "splines")) mode = spline_mode;
135   else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
136   else {
137     fprintf (stderr,
138              "%s: mode must be balls, lines, tails, polygons, splines, or\n\
139         filled-splines, not \"%s\"\n",
140              progname, mode_str);
141     exit (1);
142   }
143
144   mode_str = get_string_resource ("colorMode", "ColorMode");
145   if (! mode_str) cmode = cycle_mode;
146   else if (!strcmp (mode_str, "cycle")) cmode = cycle_mode;
147   else if (!strcmp (mode_str, "random")) cmode = random_mode;
148   else {
149     fprintf (stderr, "%s: colorMode must be cycle or random, not \"%s\"\n",
150              progname, mode_str);
151     exit (1);
152   }
153
154   if (mode != ball_mode && mode != tail_mode) glow_p = False;
155   
156   if (mode == polygon_mode && npoints < 3)
157     mode = line_mode;
158
159   if (mode != ball_mode)
160     {
161       int size = (segments ? segments : 1);
162       point_stack_size = size * (npoints + 1);
163       point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
164       point_stack_fp = 0;
165       if (segments > 0)
166         pixel_stack_size = segments;
167       else
168         pixel_stack_size = (360 / color_shift);
169       pixel_stack = (unsigned long *)
170         calloc (pixel_stack_size, sizeof (unsigned int));
171       pixel_stack_fp = 0;
172     }
173
174   gcv.line_width = (mode == tail_mode
175                     ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
176                     : 1);
177   gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
178
179   gcv.foreground = default_fg_pixel =
180     get_pixel_resource ("foreground", "Foreground", dpy, cmap);
181   draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
182   gcv.foreground = get_pixel_resource ("background", "Background", dpy, cmap);
183   erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
184
185   if (!mono_p && mode != ball_mode)
186     for (i = 0; i < pixel_stack_size; i++)
187       {
188         XColor color;
189         color.pixel = default_fg_pixel;
190         XQueryColor (dpy, cmap, &color);
191         if (!XAllocColor (dpy, cmap, &color)) abort ();
192         pixel_stack [i] = color.pixel;
193       }
194
195 #define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
196
197   if (orbit_p && !global_size)
198     /* To orbit, all objects must be the same mass, or the math gets
199        really hairy... */
200     global_size = rand_size ();
201
202   th = frand (M_PI+M_PI);
203   for (i = 0; i < npoints; i++)
204     {
205       int new_size = (global_size ? global_size : rand_size ());
206       balls [i].dx = 0;
207       balls [i].dy = 0;
208       balls [i].size = new_size;
209       balls [i].mass = (new_size * new_size * 10);
210       balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
211       balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
212       if (! orbit_p)
213         {
214           balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
215           balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
216         }
217       balls [i].color.pixel = default_fg_pixel;
218       balls [i].color.flags = DoRed | DoGreen | DoBlue;
219       if (!mono_p)
220         {
221           if (i != 0 && (glow_p || mode != ball_mode))
222             balls [i].hue = balls [0].hue;
223           else
224             balls [i].hue = random () % 360;
225           hsv_to_rgb (balls [i].hue, 1.0, 1.0,
226                       &balls [i].color.red, &balls [i].color.green,
227                       &balls [i].color.blue);
228           if (!XAllocColor (dpy, cmap, &balls [i].color))
229             mono_p = True; /* just give up */
230         }
231     }
232
233   if (orbit_p)
234     {
235       double a = 0;
236       double v;
237       double v_mult = get_float_resource ("vMult", "Float");
238       if (v_mult == 0.0) v_mult = 1.0;
239
240       for (i = 1; i < npoints; i++)
241         {
242           double _2ipi_n = (2 * i * M_PI / npoints);
243           double x = r * cos (_2ipi_n);
244           double y = r * sin (_2ipi_n);
245           double distx = r - x;
246           double dist2 = (distx * distx) + (y * y);
247           double dist = sqrt (dist2);
248           double a1 = ((balls[i].mass / dist2) *
249                        ((dist < threshold) ? -1.0 : 1.0) *
250                        (distx / dist));
251           a += a1;
252         }
253       if (a < 0.0)
254         {
255           fprintf (stderr, "%s: domain error: forces on balls too great\n",
256                    progname);
257           exit (-1);
258         }
259       v = sqrt (a * r) * v_mult;
260       for (i = 0; i < npoints; i++)
261         {
262           double k = ((2 * i * M_PI / npoints) + th);
263           balls [i].vx = -v * sin (k);
264           balls [i].vy =  v * cos (k);
265         }
266     }
267
268   if (mono_p) glow_p = False;
269   XClearWindow (dpy, window);
270 }
271
272 static void
273 compute_force (i, dx_ret, dy_ret)
274      int i;
275      float *dx_ret, *dy_ret;
276 {
277   int j;
278   *dx_ret = 0;
279   *dy_ret = 0;
280   for (j = 0; j < npoints; j++)
281     {
282       float x_dist, y_dist, dist, dist2;
283
284       if (i == j) continue;
285       x_dist = balls [j].x - balls [i].x;
286       y_dist = balls [j].y - balls [i].y;
287       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
288       dist = sqrt (dist2);
289               
290       if (dist > 0.1) /* the balls are not overlapping */
291         {
292           float new_acc = ((balls[j].mass / dist2) *
293                            ((dist < threshold) ? -1.0 : 1.0));
294           float new_acc_dist = new_acc / dist;
295           *dx_ret += new_acc_dist * x_dist;
296           *dy_ret += new_acc_dist * y_dist;
297         }
298       else
299         {               /* the balls are overlapping; move randomly */
300           *dx_ret += (frand (10.0) - 5.0);
301           *dy_ret += (frand (10.0) - 5.0);
302         }
303     }
304 }
305
306 static void
307 run_balls (dpy, window)
308      Display *dpy;
309      Window window;
310 {
311   int last_point_stack_fp = point_stack_fp;
312   static int tick = 500, xlim, ylim;
313   static Colormap cmap;
314   int i;
315
316   if (tick++ == 500)
317     {
318       XWindowAttributes xgwa;
319       XGetWindowAttributes (dpy, window, &xgwa);
320       tick = 0;
321       xlim = xgwa.width;
322       ylim = xgwa.height;
323       cmap = xgwa.colormap;
324     }
325
326   /* compute the force of attraction/repulsion among all balls */
327   for (i = 0; i < npoints; i++)
328     compute_force (i, &balls[i].dx, &balls[i].dy);
329
330   /* move the balls according to the forces now in effect */
331   for (i = 0; i < npoints; i++)
332     {
333       float old_x = balls[i].x;
334       float old_y = balls[i].y;
335       float new_x, new_y;
336       int size = balls[i].size;
337       balls[i].vx += balls[i].dx;
338       balls[i].vy += balls[i].dy;
339
340       /* don't let them get too fast: impose a terminal velocity
341          (actually, make the medium have friction) */
342       if (balls[i].vx > 10)
343         {
344           balls[i].vx *= 0.9;
345           balls[i].dx = 0;
346         }
347       if (balls[i].vy > 10)
348         {
349           balls[i].vy *= 0.9;
350           balls[i].dy = 0;
351         }
352
353       balls[i].x += balls[i].vx;
354       balls[i].y += balls[i].vy;
355
356       /* bounce off the walls */
357       if (balls[i].x >= (xlim - balls[i].size))
358         {
359           balls[i].x = (xlim - balls[i].size - 1);
360           if (balls[i].vx > 0)
361             balls[i].vx = -balls[i].vx;
362         }
363       if (balls[i].y >= (ylim - balls[i].size))
364         {
365           balls[i].y = (ylim - balls[i].size - 1);
366           if (balls[i].vy > 0)
367             balls[i].vy = -balls[i].vy;
368         }
369       if (balls[i].x <= 0)
370         {
371           balls[i].x = 0;
372           if (balls[i].vx < 0)
373             balls[i].vx = -balls[i].vx;
374         }
375       if (balls[i].y <= 0)
376         {
377           balls[i].y = 0;
378           if (balls[i].vy < 0)
379             balls[i].vy = -balls[i].vy;
380         }
381
382       new_x = balls[i].x;
383       new_y = balls[i].y;
384
385       /* make color saturation be related to particle acceleration. */
386       if (glow_p)
387         {
388           float limit = 0.5;
389           double s, v, fraction;
390           float vx = balls [i].dx;
391           float vy = balls [i].dy;
392           XColor new_color;
393           if (vx < 0) vx = -vx;
394           if (vy < 0) vy = -vy;
395           fraction = vx + vy;
396           if (fraction > limit) fraction = limit;
397
398           s = 1 - (fraction / limit);
399           v = 1.0;
400
401           s = (s * 0.75) + 0.25;
402
403           hsv_to_rgb (balls [i].hue, s, v, 
404                       &new_color.red, &new_color.green, &new_color.blue);
405           if (XAllocColor (dpy, cmap, &new_color))
406             {
407               XFreeColors (dpy, cmap, &balls [i].color.pixel, 1, 0);
408               balls [i].color = new_color;
409             }
410         }
411
412       if (mode == ball_mode)
413         {
414           if (!mono_p)
415             XSetForeground (dpy, draw_gc, balls [i].color.pixel);
416           XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
417                     size, size, 0, 360*64);
418           XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
419                     size, size, 0, 360*64);
420         }
421       if (mode != ball_mode)
422         {
423           point_stack [point_stack_fp].x = new_x;
424           point_stack [point_stack_fp].y = new_y;
425           point_stack_fp++;
426         }
427     }
428
429   /* draw the lines or polygons after computing all points */
430   if (mode != ball_mode)
431     {
432       point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
433       point_stack [point_stack_fp].y = balls [0].y;
434       point_stack_fp++;
435       if (point_stack_fp == point_stack_size)
436         point_stack_fp = 0;
437       else if (point_stack_fp > point_stack_size) /* better be aligned */
438         abort ();
439       if (!mono_p)
440         {
441           XColor color2;
442           color2 = balls [0].color;
443           switch (cmode)
444             {
445             case cycle_mode:
446               cycle_hue (&color2, color_shift);
447               break;
448             case random_mode:
449               color2.red =   random () % 65535;
450               color2.green = random () % 65535;
451               color2.blue =  random () % 65535;
452               break;
453             default:
454               abort ();
455             }
456               
457           if (!XAllocColor (dpy, cmap, &color2))
458             {
459               color2 = balls [0].color;
460               if (!XAllocColor (dpy, cmap, &balls [0].color))
461                 abort ();
462             }
463           pixel_stack [pixel_stack_fp++] = balls [0].color.pixel;
464           if (pixel_stack_fp >= pixel_stack_size)
465             pixel_stack_fp = 0;
466           XFreeColors (dpy, cmap, pixel_stack + pixel_stack_fp, 1, 0);
467           balls [0].color = color2;
468           XSetForeground (dpy, draw_gc, balls [0].color.pixel);
469         }
470     }
471
472   switch (mode)
473     {
474     case ball_mode:
475       break;
476     case line_mode:
477       if (segments > 0)
478         XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
479                     npoints + 1, CoordModeOrigin);
480       XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
481                   npoints + 1, CoordModeOrigin);
482       break;
483     case polygon_mode:
484       if (segments > 0)
485         XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
486                       npoints + 1, (npoints == 3 ? Convex : Complex),
487                       CoordModeOrigin);
488       XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
489                     npoints + 1, (npoints == 3 ? Convex : Complex),
490                     CoordModeOrigin);
491       break;
492     case tail_mode:
493       {
494         int i;
495         for (i = 0; i < npoints; i++)
496           {
497             int index = point_stack_fp + i;
498             int next_index = (index + (npoints + 1)) % point_stack_size;
499             XDrawLine (dpy, window, erase_gc,
500                        point_stack [index].x,
501                        point_stack [index].y,
502                        point_stack [next_index].x,
503                        point_stack [next_index].y);
504
505             index = last_point_stack_fp + i;
506             next_index = (index - (npoints + 1)) % point_stack_size;
507             if (next_index < 0) next_index += point_stack_size;
508             if (point_stack [next_index].x == 0 &&
509                 point_stack [next_index].y == 0)
510               continue;
511             XDrawLine (dpy, window, draw_gc,
512                        point_stack [index].x,
513                        point_stack [index].y,
514                        point_stack [next_index].x,
515                        point_stack [next_index].y);
516           }
517       }
518       break;
519     case spline_mode:
520     case spline_filled_mode:
521       {
522         int i;
523         static spline *s = 0;
524         if (! s) s = make_spline (npoints);
525         if (segments > 0)
526           {
527             for (i = 0; i < npoints; i++)
528               {
529                 s->control_x [i] = point_stack [point_stack_fp + i].x;
530                 s->control_y [i] = point_stack [point_stack_fp + i].y;
531               }
532             compute_closed_spline (s);
533             if (mode == spline_filled_mode)
534               XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
535                             (s->n_points == 3 ? Convex : Complex),
536                             CoordModeOrigin);
537             else
538               XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
539                           CoordModeOrigin);
540           }
541         for (i = 0; i < npoints; i++)
542           {
543             s->control_x [i] = point_stack [last_point_stack_fp + i].x;
544             s->control_y [i] = point_stack [last_point_stack_fp + i].y;
545           }
546         compute_closed_spline (s);
547         if (mode == spline_filled_mode)
548           XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
549                         (s->n_points == 3 ? Convex : Complex),
550                         CoordModeOrigin);
551         else
552           XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
553                       CoordModeOrigin);
554       }
555       break;
556     default:
557       abort ();
558     }
559
560   XSync (dpy, True);
561 }
562
563 \f
564 char *progclass = "Attraction";
565
566 char *defaults [] = {
567   "Attraction.background:       black",         /* to placate SGI */
568   "Attraction.foreground:       white",
569   "*mode:       balls",
570   "*points:     0",
571   "*size:       0",
572   "*threshold:  100",
573   "*delay:      10000",
574   "*glow:       false",
575   "*orbit:      false",
576   "*colorShift: 3",
577   "*segments:   100",
578   0
579 };
580
581 XrmOptionDescRec options [] = {
582   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
583   { "-points",          ".points",      XrmoptionSepArg, 0 },
584   { "-threshold",       ".threshold",   XrmoptionSepArg, 0 },
585   { "-segments",        ".segments",    XrmoptionSepArg, 0 },
586   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
587   { "-size",            ".size",        XrmoptionSepArg, 0 },
588   { "-color-mode",      ".colorMode",   XrmoptionSepArg, 0 },
589   { "-color-shift",     ".colorShift",  XrmoptionSepArg, 0 },
590   { "-radius",          ".radius",      XrmoptionSepArg, 0 },
591   { "-vx",              ".vx",          XrmoptionSepArg, 0 },
592   { "-vy",              ".vy",          XrmoptionSepArg, 0 },
593   { "-vmult",           ".vMult",       XrmoptionSepArg, 0 },
594   { "-glow",            ".glow",        XrmoptionNoArg, "true" },
595   { "-noglow",          ".glow",        XrmoptionNoArg, "false" },
596   { "-orbit",           ".orbit",       XrmoptionNoArg, "true" }
597 };
598 int options_size = (sizeof (options) / sizeof (options[0]));
599
600 void
601 screenhack (dpy, window)
602      Display *dpy;
603      Window window;
604 {
605   init_balls (dpy, window);
606   while (1)
607     {
608       run_balls (dpy, window);
609       if (delay) usleep (delay);
610     }
611 }