ftp://ftp.uni-heidelberg.de/pub/X11/contrib/applications/xscreensaver-2.07.tar.gz
[xscreensaver] / hacks / attraction.c
diff --git a/hacks/attraction.c b/hacks/attraction.c
new file mode 100644 (file)
index 0000000..4293004
--- /dev/null
@@ -0,0 +1,666 @@
+/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997
+ *  Jamie Zawinski <jwz@netscape.com>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+/* Simulation of a pair of quasi-gravitational fields, maybe sorta kinda
+   a little like the strong and weak electromagnetic forces.  Derived from
+   a Lispm screensaver by John Pezaris <pz@mit.edu>.  Mouse control and
+   viscosity added by "Philip Edward Cutone, III" <pc2d+@andrew.cmu.edu>.
+
+   John sez:
+
+   The simulation started out as a purely accurate gravitational simulation,
+   but, with constant simulation step size, I quickly realized the field being
+   simulated while grossly gravitational was, in fact, non-conservative.  It
+   also had the rather annoying behavior of dealing very badly with colliding
+   orbs.  Therefore, I implemented a negative-gravity region (with two
+   thresholds; as I read your code, you only implemented one) to prevent orbs
+   from every coming too close together, and added a viscosity factor if the
+   speed of any orb got too fast.  This provides a nice stable system with
+   interesting behavior.
+
+   I had experimented with a number of fields including the van der Waals
+   force (very interesting orbiting behavior) and 1/r^3 gravity (not as
+   interesting as 1/r^2).  An even normal viscosity (rather than the
+   thresholded version to bleed excess energy) is also not interesting.
+   The 1/r^2, -1/r^2, -10/r^2 thresholds proved not only robust but also
+   interesting -- the orbs never collided and the threshold viscosity fixed
+   the non-conservational problem.
+
+   Philip sez:
+   > An even normal viscosity (rather than the thresholded version to
+   > bleed excess energy) is also not interesting.
+
+   unless you make about 200 points.... set the viscosity to about .8
+   and drag the mouse through it.   it makes a nice wave which travels
+   through the field.
+
+   And (always the troublemaker) Joe Keane <jgk@jgk.org> sez:
+
+   Despite what John sez, the field being simulated is always conservative.
+   The real problem is that it uses a simple hack, computing acceleration
+   *based only on the starting position*, instead of a real differential
+   equation solver.  Thus you'll always have energy coming out of nowhere,
+   although it's most blatant when balls get close together.  If it were
+   done right, you wouldn't need viscosity or artificial limits on how
+   close the balls can get.
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include "screenhack.h"
+#include "spline.h"
+
+struct ball {
+  double x, y;
+  double vx, vy;
+  double dx, dy;
+  double mass;
+  int size;
+  int pixel_index;
+  int hue;
+};
+
+static struct ball *balls;
+static int npoints;
+static int threshold;
+static int delay;
+static int global_size;
+static int segments;
+static Bool glow_p;
+static Bool orbit_p;
+static XPoint *point_stack;
+static int point_stack_size, point_stack_fp;
+static XColor *colors;
+static int ncolors;
+static int fg_index;
+static int color_shift;
+
+/*flip mods for mouse interaction*/
+static Bool mouse_p;
+int mouse_x, mouse_y, mouse_mass, root_x, root_y;
+static double viscosity;
+
+static enum object_mode {
+  ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
+  tail_mode
+} mode;
+
+static GC draw_gc, erase_gc;
+
+#define MAX_SIZE 16
+
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)>(b)?(a):(b))
+
+static void
+init_balls (Display *dpy, Window window)
+{
+  int i;
+  XWindowAttributes xgwa;
+  XGCValues gcv;
+  int xlim, ylim, midx, midy, r, vx, vy;
+  double th;
+  Colormap cmap;
+  char *mode_str;
+  XGetWindowAttributes (dpy, window, &xgwa);
+  xlim = xgwa.width;
+  ylim = xgwa.height;
+  cmap = xgwa.colormap;
+  midx = xlim/2;
+  midy = ylim/2;
+  r = get_integer_resource ("radius", "Integer");
+  if (r <= 0 || r > min (xlim/2, ylim/2))
+    r = min (xlim/2, ylim/2) - 50;
+  vx = get_integer_resource ("vx", "Integer");
+  vy = get_integer_resource ("vy", "Integer");
+  npoints = get_integer_resource ("points", "Integer");
+  if (npoints < 1)
+    npoints = 3 + (random () % 5);
+  balls = (struct ball *) malloc (npoints * sizeof (struct ball));
+  segments = get_integer_resource ("segments", "Integer");
+  if (segments < 0) segments = 1;
+  threshold = get_integer_resource ("threshold", "Integer");
+  if (threshold < 0) threshold = 0;
+  delay = get_integer_resource ("delay", "Integer");
+    if (delay < 0) delay = 0;
+  global_size = get_integer_resource ("size", "Integer");
+  if (global_size < 0) global_size = 0;
+  glow_p = get_boolean_resource ("glow", "Boolean");
+  orbit_p = get_boolean_resource ("orbit", "Boolean");
+  color_shift = get_integer_resource ("colorShift", "Integer");
+  if (color_shift <= 0) color_shift = 5;
+
+  /*flip mods for mouse interaction*/
+  mouse_p = get_boolean_resource ("mouse", "Boolean");
+  mouse_mass = get_integer_resource ("mouseSize", "Integer");
+  mouse_mass =  mouse_mass *  mouse_mass *10;
+
+  viscosity = get_float_resource ("viscosity", "Float");
+
+  mode_str = get_string_resource ("mode", "Mode");
+  if (! mode_str) mode = ball_mode;
+  else if (!strcmp (mode_str, "balls")) mode = ball_mode;
+  else if (!strcmp (mode_str, "lines")) mode = line_mode;
+  else if (!strcmp (mode_str, "polygons")) mode = polygon_mode;
+  else if (!strcmp (mode_str, "tails")) mode = tail_mode;
+  else if (!strcmp (mode_str, "splines")) mode = spline_mode;
+  else if (!strcmp (mode_str, "filled-splines")) mode = spline_filled_mode;
+  else {
+    fprintf (stderr,
+            "%s: mode must be balls, lines, tails, polygons, splines, or\n\
+       filled-splines, not \"%s\"\n",
+            progname, mode_str);
+    exit (1);
+  }
+
+  if (mode != ball_mode && mode != tail_mode) glow_p = False;
+  
+  if (mode == polygon_mode && npoints < 3)
+    mode = line_mode;
+
+  ncolors = get_integer_resource ("colors", "Colors");
+  if (ncolors < 2) ncolors = 2;
+  if (ncolors <= 2) mono_p = True;
+  colors = 0;
+
+  if (!mono_p)
+    {
+      fg_index = 0;
+      switch (mode)
+       {
+       case ball_mode:
+         if (glow_p)
+           {
+             int H = random() % 360;
+             double S1 = 0.25;
+             double S2 = 1.00;
+             double V = frand(0.25) + 0.75;
+             colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
+             make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, colors, &ncolors,
+                              False, True, False);
+           }
+         else
+           {
+             ncolors = npoints;
+             colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
+             make_random_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
+                                   True, True, False, True);
+           }
+         break;
+       case line_mode:
+       case polygon_mode:
+       case spline_mode:
+       case spline_filled_mode:
+       case tail_mode:
+         colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
+         make_smooth_colormap (dpy, xgwa.visual, cmap, colors, &ncolors,
+                               True, False, True);
+         break;
+       default:
+         abort ();
+       }
+    }
+
+  if (!mono_p && ncolors <= 2)
+    {
+      if (colors) free (colors);
+      colors = 0;
+      mono_p = True;
+    }
+
+  if (mode != ball_mode)
+    {
+      int size = (segments ? segments : 1);
+      point_stack_size = size * (npoints + 1);
+      point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
+      point_stack_fp = 0;
+    }
+
+  gcv.line_width = (mode == tail_mode
+                   ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
+                   : 1);
+  gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
+
+  if (mono_p)
+    gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap);
+  else
+    gcv.foreground = colors[fg_index].pixel;
+  draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
+
+  gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap);
+  erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
+
+
+#define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
+
+  if (orbit_p && !global_size)
+    /* To orbit, all objects must be the same mass, or the math gets
+       really hairy... */
+    global_size = rand_size ();
+
+  th = frand (M_PI+M_PI);
+  for (i = 0; i < npoints; i++)
+    {
+      int new_size = (global_size ? global_size : rand_size ());
+      balls [i].dx = 0;
+      balls [i].dy = 0;
+      balls [i].size = new_size;
+      balls [i].mass = (new_size * new_size * 10);
+      balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / npoints) + th);
+      balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / npoints) + th);
+      if (! orbit_p)
+       {
+         balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
+         balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
+       }
+      if (mono_p || mode != ball_mode)
+       balls [i].pixel_index = -1;
+      else if (glow_p)
+       balls [i].pixel_index = 0;
+      else
+       balls [i].pixel_index = random() % ncolors;
+    }
+
+  if (orbit_p)
+    {
+      double a = 0;
+      double v;
+      double v_mult = get_float_resource ("vMult", "Float");
+      if (v_mult == 0.0) v_mult = 1.0;
+
+      for (i = 1; i < npoints; i++)
+       {
+          double _2ipi_n = (2 * i * M_PI / npoints);
+         double x = r * cos (_2ipi_n);
+         double y = r * sin (_2ipi_n);
+         double distx = r - x;
+         double dist2 = (distx * distx) + (y * y);
+          double dist = sqrt (dist2);
+          double a1 = ((balls[i].mass / dist2) *
+                       ((dist < threshold) ? -1.0 : 1.0) *
+                       (distx / dist));
+         a += a1;
+       }
+      if (a < 0.0)
+       {
+         fprintf (stderr, "%s: domain error: forces on balls too great\n",
+                  progname);
+         exit (-1);
+       }
+      v = sqrt (a * r) * v_mult;
+      for (i = 0; i < npoints; i++)
+       {
+         double k = ((2 * i * M_PI / npoints) + th);
+         balls [i].vx = -v * sin (k);
+         balls [i].vy =  v * cos (k);
+       }
+    }
+
+  if (mono_p) glow_p = False;
+  XClearWindow (dpy, window);
+}
+
+static void
+compute_force (int i, double *dx_ret, double *dy_ret)
+{
+  int j;
+  double x_dist, y_dist, dist, dist2;
+  *dx_ret = 0;
+  *dy_ret = 0;
+  for (j = 0; j < npoints; j++)
+    {
+      if (i == j) continue;
+      x_dist = balls [j].x - balls [i].x;
+      y_dist = balls [j].y - balls [i].y;
+      dist2 = (x_dist * x_dist) + (y_dist * y_dist);
+      dist = sqrt (dist2);
+             
+      if (dist > 0.1) /* the balls are not overlapping */
+       {
+         double new_acc = ((balls[j].mass / dist2) *
+                           ((dist < threshold) ? -1.0 : 1.0));
+         double new_acc_dist = new_acc / dist;
+         *dx_ret += new_acc_dist * x_dist;
+         *dy_ret += new_acc_dist * y_dist;
+       }
+      else
+       {               /* the balls are overlapping; move randomly */
+         *dx_ret += (frand (10.0) - 5.0);
+         *dy_ret += (frand (10.0) - 5.0);
+       }
+    }
+
+  if (mouse_p)
+    {
+      x_dist = mouse_x - balls [i].x;
+      y_dist = mouse_y - balls [i].y;
+      dist2 = (x_dist * x_dist) + (y_dist * y_dist);
+      dist = sqrt (dist2);
+       
+      if (dist > 0.1) /* the balls are not overlapping */
+       {
+         double new_acc = ((mouse_mass / dist2) *
+                           ((dist < threshold) ? -1.0 : 1.0));
+         double new_acc_dist = new_acc / dist;
+         *dx_ret += new_acc_dist * x_dist;
+         *dy_ret += new_acc_dist * y_dist;
+       }
+      else
+       {               /* the balls are overlapping; move randomly */
+         *dx_ret += (frand (10.0) - 5.0);
+         *dy_ret += (frand (10.0) - 5.0);
+       }
+    }
+}
+
+static void
+run_balls (Display *dpy, Window window)
+{
+  int last_point_stack_fp = point_stack_fp;
+  static int tick = 500, xlim, ylim;
+  static Colormap cmap;
+  int i;
+
+  /*flip mods for mouse interaction*/
+  Window  root1, child1;
+  unsigned int mask;
+  if (mouse_p)
+    {
+      XQueryPointer(dpy, window, &root1, &child1,
+                   &root_x, &root_y, &mouse_x, &mouse_y, &mask);
+    }
+
+  if (tick++ == 500)
+    {
+      XWindowAttributes xgwa;
+      XGetWindowAttributes (dpy, window, &xgwa);
+      tick = 0;
+      xlim = xgwa.width;
+      ylim = xgwa.height;
+      cmap = xgwa.colormap;
+    }
+
+  /* compute the force of attraction/repulsion among all balls */
+  for (i = 0; i < npoints; i++)
+    compute_force (i, &balls[i].dx, &balls[i].dy);
+
+  /* move the balls according to the forces now in effect */
+  for (i = 0; i < npoints; i++)
+    {
+      double old_x = balls[i].x;
+      double old_y = balls[i].y;
+      double new_x, new_y;
+      int size = balls[i].size;
+      balls[i].vx += balls[i].dx;
+      balls[i].vy += balls[i].dy;
+
+      /* don't let them get too fast: impose a terminal velocity
+         (actually, make the medium have friction) */
+      if (balls[i].vx > 10)
+       {
+         balls[i].vx *= 0.9;
+         balls[i].dx = 0;
+       }
+      else if (viscosity != 1)
+       {
+         balls[i].vx *= viscosity;
+       }
+
+      if (balls[i].vy > 10)
+       {
+         balls[i].vy *= 0.9;
+         balls[i].dy = 0;
+       }
+      else if (viscosity != 1)
+       {
+         balls[i].vy *= viscosity;
+       }
+
+      balls[i].x += balls[i].vx;
+      balls[i].y += balls[i].vy;
+
+      /* bounce off the walls */
+      if (balls[i].x >= (xlim - balls[i].size))
+       {
+         balls[i].x = (xlim - balls[i].size - 1);
+         if (balls[i].vx > 0)
+           balls[i].vx = -balls[i].vx;
+       }
+      if (balls[i].y >= (ylim - balls[i].size))
+       {
+         balls[i].y = (ylim - balls[i].size - 1);
+         if (balls[i].vy > 0)
+           balls[i].vy = -balls[i].vy;
+       }
+      if (balls[i].x <= 0)
+       {
+         balls[i].x = 0;
+         if (balls[i].vx < 0)
+           balls[i].vx = -balls[i].vx;
+       }
+      if (balls[i].y <= 0)
+       {
+         balls[i].y = 0;
+         if (balls[i].vy < 0)
+           balls[i].vy = -balls[i].vy;
+       }
+
+      new_x = balls[i].x;
+      new_y = balls[i].y;
+
+      if (!mono_p)
+       {
+         if (mode == ball_mode)
+           {
+             if (glow_p)
+               {
+                 /* make color saturation be related to particle
+                    acceleration. */
+                 double limit = 0.5;
+                 double s, fraction;
+                 double vx = balls [i].dx;
+                 double vy = balls [i].dy;
+                 if (vx < 0) vx = -vx;
+                 if (vy < 0) vy = -vy;
+                 fraction = vx + vy;
+                 if (fraction > limit) fraction = limit;
+
+                 s = 1 - (fraction / limit);
+                 balls[i].pixel_index = (ncolors * s);
+               }
+             XSetForeground (dpy, draw_gc,
+                             colors[balls[i].pixel_index].pixel);
+           }
+       }
+
+      if (mode == ball_mode)
+       {
+         XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
+                   size, size, 0, 360*64);
+         XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
+                   size, size, 0, 360*64);
+       }
+      else
+       {
+         point_stack [point_stack_fp].x = new_x;
+         point_stack [point_stack_fp].y = new_y;
+         point_stack_fp++;
+       }
+    }
+
+  /* draw the lines or polygons after computing all points */
+  if (mode != ball_mode)
+    {
+      point_stack [point_stack_fp].x = balls [0].x; /* close the polygon */
+      point_stack [point_stack_fp].y = balls [0].y;
+      point_stack_fp++;
+      if (point_stack_fp == point_stack_size)
+       point_stack_fp = 0;
+      else if (point_stack_fp > point_stack_size) /* better be aligned */
+       abort ();
+      if (!mono_p)
+       {
+         static int tick = 0;
+         if (tick++ == color_shift)
+           {
+             tick = 0;
+             fg_index = (fg_index + 1) % ncolors;
+             XSetForeground (dpy, draw_gc, colors[fg_index].pixel);
+           }
+       }
+    }
+
+  switch (mode)
+    {
+    case ball_mode:
+      break;
+    case line_mode:
+      if (segments > 0)
+       XDrawLines (dpy, window, erase_gc, point_stack + point_stack_fp,
+                   npoints + 1, CoordModeOrigin);
+      XDrawLines (dpy, window, draw_gc, point_stack + last_point_stack_fp,
+                 npoints + 1, CoordModeOrigin);
+      break;
+    case polygon_mode:
+      if (segments > 0)
+       XFillPolygon (dpy, window, erase_gc, point_stack + point_stack_fp,
+                     npoints + 1, (npoints == 3 ? Convex : Complex),
+                     CoordModeOrigin);
+      XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
+                   npoints + 1, (npoints == 3 ? Convex : Complex),
+                   CoordModeOrigin);
+      break;
+    case tail_mode:
+      {
+       for (i = 0; i < npoints; i++)
+         {
+           int index = point_stack_fp + i;
+           int next_index = (index + (npoints + 1)) % point_stack_size;
+           XDrawLine (dpy, window, erase_gc,
+                      point_stack [index].x,
+                      point_stack [index].y,
+                      point_stack [next_index].x,
+                      point_stack [next_index].y);
+
+           index = last_point_stack_fp + i;
+           next_index = (index - (npoints + 1)) % point_stack_size;
+           if (next_index < 0) next_index += point_stack_size;
+           if (point_stack [next_index].x == 0 &&
+               point_stack [next_index].y == 0)
+             continue;
+           XDrawLine (dpy, window, draw_gc,
+                      point_stack [index].x,
+                      point_stack [index].y,
+                      point_stack [next_index].x,
+                      point_stack [next_index].y);
+         }
+      }
+      break;
+    case spline_mode:
+    case spline_filled_mode:
+      {
+       static spline *s = 0;
+       if (! s) s = make_spline (npoints);
+       if (segments > 0)
+         {
+           for (i = 0; i < npoints; i++)
+             {
+               s->control_x [i] = point_stack [point_stack_fp + i].x;
+               s->control_y [i] = point_stack [point_stack_fp + i].y;
+             }
+           compute_closed_spline (s);
+           if (mode == spline_filled_mode)
+             XFillPolygon (dpy, window, erase_gc, s->points, s->n_points,
+                           (s->n_points == 3 ? Convex : Complex),
+                           CoordModeOrigin);
+           else
+             XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
+                         CoordModeOrigin);
+         }
+       for (i = 0; i < npoints; i++)
+         {
+           s->control_x [i] = point_stack [last_point_stack_fp + i].x;
+           s->control_y [i] = point_stack [last_point_stack_fp + i].y;
+         }
+       compute_closed_spline (s);
+       if (mode == spline_filled_mode)
+         XFillPolygon (dpy, window, draw_gc, s->points, s->n_points,
+                       (s->n_points == 3 ? Convex : Complex),
+                       CoordModeOrigin);
+       else
+         XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
+                     CoordModeOrigin);
+      }
+      break;
+    default:
+      abort ();
+    }
+
+  XSync (dpy, True);
+}
+
+\f
+char *progclass = "Attraction";
+
+char *defaults [] = {
+  "Attraction.background:      black",         /* to placate SGI */
+  "Attraction.foreground:      white",
+  "*mode:      balls",
+  "*points:    0",
+  "*size:      0",
+  "*colors:    200",
+  "*threshold: 100",
+  "*delay:     10000",
+  "*glow:      false",
+  "*mouseSize: 10",
+  "*mouse:     false",
+  "*viscosity: 1",
+  "*orbit:     false",
+  "*colorShift:        3",
+  "*segments:  500",
+  "*vMult:     0.9",
+  0
+};
+
+XrmOptionDescRec options [] = {
+  { "-mode",           ".mode",        XrmoptionSepArg, 0 },
+  { "-colors",         ".colors",      XrmoptionSepArg, 0 },
+  { "-points",         ".points",      XrmoptionSepArg, 0 },
+  { "-color-shift",    ".colorShift",  XrmoptionSepArg, 0 },
+  { "-threshold",      ".threshold",   XrmoptionSepArg, 0 },
+  { "-segments",       ".segments",    XrmoptionSepArg, 0 },
+  { "-delay",          ".delay",       XrmoptionSepArg, 0 },
+  { "-size",           ".size",        XrmoptionSepArg, 0 },
+  { "-radius",         ".radius",      XrmoptionSepArg, 0 },
+  { "-vx",             ".vx",          XrmoptionSepArg, 0 },
+  { "-vy",             ".vy",          XrmoptionSepArg, 0 },
+  { "-vmult",          ".vMult",       XrmoptionSepArg, 0 },
+  { "-mouse-size",     ".mouseSize",   XrmoptionSepArg, 0 },
+  { "-mouse",          ".mouse",       XrmoptionNoArg, "true" },
+  { "-nomouse",                ".mouse",       XrmoptionNoArg, "false" },
+  { "-viscosity",      ".viscosity",   XrmoptionSepArg, 0 },
+  { "-glow",           ".glow",        XrmoptionNoArg, "true" },
+  { "-noglow",         ".glow",        XrmoptionNoArg, "false" },
+  { "-orbit",          ".orbit",       XrmoptionNoArg, "true" },
+  { 0, 0, 0, 0 }
+};
+
+void
+screenhack (Display *dpy, Window window)
+{
+  init_balls (dpy, window);
+  while (1)
+    {
+      run_balls (dpy, window);
+      if (delay) usleep (delay);
+    }
+}