ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / hacks / attraction.c
index 1f93781df3673ac485d603044339651813930eda..8e25d244f1d11aa3544de2c9a4cb7486be218637 100644 (file)
@@ -1,4 +1,5 @@
-/* xscreensaver, Copyright (c) 1992 Jamie Zawinski <jwz@lucid.com>
+/* xscreensaver, Copyright (c) 1992, 1995, 1996, 1997, 1998, 2001, 2006
+ *  Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 
 /* 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>.
+   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.
- */
+       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.
+
+   Matt <straitm@carleton.edu> sez:
+
+       Added a switch to remove the walls.
+
+       Added a switch to make the threshold viscosity optional.  If
+       nomaxspeed is specified, then balls going really fast do not
+       recieve special treatment.
+
+       I've made tail mode prettier by eliminating the first erase line
+       that drew from the upper left corner to the starting position of
+       each point.
+
+       Made the balls in modes other than "balls" bounce exactly at the
+       walls.  (Because the graphics for different modes are drawn
+       differently with respect to the "actual" position of the point,
+       they used to be able to run somewhat past the walls, or bounce
+       before hitting them.)
+
+       Added an option to output each ball's speed in the form of a bar
+       graph drawn on the same window as the balls.  If only x or y is
+       selected, they will be represented on the appropriate axis down
+       the center of the window.  If both are selected, they will both
+       be displayed along the diagonal such that the x and y bars for
+       each point start at the same place.  If speed is selected, the
+       speed will be displayed down the left side.  */
 
-#include "screenhack.h"
-#include "spline.h"
 #include <stdio.h>
 #include <math.h>
-#if __STDC__
-#include <values.h>
-#endif
+#include "screenhack.h"
+#include "spline.h"
+
+/* The normal (and max) width for a graph bar */
+#define BAR_SIZE 11
+#define MAX_SIZE 16
+#define min(a,b) ((a)<(b)?(a):(b))
+#define max(a,b) ((a)>(b)?(a):(b))
+
+
+enum object_mode {
+  ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
+  tail_mode
+};
+
+enum graph_mode {
+  graph_none, graph_x, graph_y, graph_both, graph_speed
+};
 
 struct ball {
-  float x, y;
-  float vx, vy;
-  float dx, dy;
-  float mass;
+  double x, y;
+  double vx, vy;
+  double dx, dy;
+  double mass;
   int size;
-  XColor color;
+  int pixel_index;
   int hue;
 };
 
-static unsigned int default_fg_pixel;
-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, pixel_stack_fp, pixel_stack_size;
-static unsigned long *pixel_stack;
-static unsigned int color_shift;
-
-static enum object_mode {
-  ball_mode, line_mode, polygon_mode, spline_mode, spline_filled_mode,
-  tail_mode
-} mode;
+struct state {
+  struct ball *balls;
+  double *x_vels;
+  double *y_vels;
+  double *speeds;
+  int npoints;
+  int threshold;
+  int delay;
+  int global_size;
+  int segments;
+  Bool glow_p;
+  Bool orbit_p;
+  Bool walls_p;
+  Bool maxspeed_p;
+  Bool cbounce_p;
+  XPoint *point_stack;
+  int point_stack_size, point_stack_fp;
+  XColor *colors;
+  int ncolors;
+  int fg_index;
+  int color_shift;
+  int xlim, ylim;
+  Bool no_erase_yet; /* for tail mode fix */
 
-static enum color_mode {
-  cycle_mode, random_mode
-} cmode;
+  /*flip mods for mouse interaction*/
+  Bool mouse_p;
+  int mouse_x, mouse_y, mouse_mass, root_x, root_y;
+  double viscosity;
 
-static GC draw_gc, erase_gc;
+  enum object_mode mode;
+  enum graph_mode graph_mode;
 
-#define MAX_SIZE 16
+  GC draw_gc, erase_gc;
 
-#define min(a,b) ((a)<(b)?(a):(b))
-#define max(a,b) ((a)>(b)?(a):(b))
+  int total_ticks;
+  int color_tick;
+  spline *spl;
+};
 
-static void
-init_balls (dpy, window)
-     Display *dpy;
-     Window window;
+
+static void *
+attraction_init (Display *dpy, Window window)
 {
+  struct state *st = (struct state *) calloc (1, sizeof(*st));
   int i;
   XWindowAttributes xgwa;
   XGCValues gcv;
-  int xlim, ylim, midx, midy, r, vx, vy;
+  int midx, midy, r, vx, vy;
   double th;
   Colormap cmap;
-  char *mode_str;
+  char *mode_str, *graph_mode_str;
+
   XGetWindowAttributes (dpy, window, &xgwa);
-  xlim = xgwa.width;
-  ylim = xgwa.height;
+  st->xlim = xgwa.width;
+  st->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 >= 360) color_shift = 5;
-
-  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;
+  midx = st->xlim/2;
+  midy = st->ylim/2;
+  st->walls_p = get_boolean_resource (dpy, "walls", "Boolean");
+
+  /* if there aren't walls, don't set a limit on the radius */
+  r = get_integer_resource (dpy, "radius", "Integer");
+  if (r <= 0 || (r > min (st->xlim/2, st->ylim/2) && st->walls_p) )
+    r = min (st->xlim/2, st->ylim/2) - 50;
+
+  vx = get_integer_resource (dpy, "vx", "Integer");
+  vy = get_integer_resource (dpy, "vy", "Integer");
+
+  st->npoints = get_integer_resource (dpy, "points", "Integer");
+  if (st->npoints < 1)
+    st->npoints = 3 + (random () % 5);
+  st->balls = (struct ball *) malloc (st->npoints * sizeof (struct ball));
+
+  st->no_erase_yet = 1; /* for tail mode fix */
+
+  st->segments = get_integer_resource (dpy, "segments", "Integer");
+  if (st->segments < 0) st->segments = 1;
+
+  st->threshold = get_integer_resource (dpy, "threshold", "Integer");
+  if (st->threshold < 0) st->threshold = 0;
+
+  st->delay = get_integer_resource (dpy, "delay", "Integer");
+  if (st->delay < 0) st->delay = 0;
+
+  st->global_size = get_integer_resource (dpy, "size", "Integer");
+  if (st->global_size < 0) st->global_size = 0;
+
+  st->glow_p = get_boolean_resource (dpy, "glow", "Boolean");
+
+  st->orbit_p = get_boolean_resource (dpy, "orbit", "Boolean");
+
+  st->maxspeed_p = get_boolean_resource (dpy, "maxspeed", "Boolean");
+
+  st->cbounce_p = get_boolean_resource (dpy, "cbounce", "Boolean");
+
+  st->color_shift = get_integer_resource (dpy, "colorShift", "Integer");
+  if (st->color_shift <= 0) st->color_shift = 5;
+
+  /*flip mods for mouse interaction*/
+  st->mouse_p = get_boolean_resource (dpy, "mouse", "Boolean");
+  st->mouse_mass = get_integer_resource (dpy, "mouseSize", "Integer");
+  st->mouse_mass =  st->mouse_mass *  st->mouse_mass *10;
+
+  st->viscosity = get_float_resource (dpy, "viscosity", "Float");
+
+  mode_str = get_string_resource (dpy, "mode", "Mode");
+  if (! mode_str) st->mode = ball_mode;
+  else if (!strcmp (mode_str, "balls"))        st->mode = ball_mode;
+  else if (!strcmp (mode_str, "lines"))        st->mode = line_mode;
+  else if (!strcmp (mode_str, "polygons"))     st->mode = polygon_mode;
+  else if (!strcmp (mode_str, "tails"))        st->mode = tail_mode;
+  else if (!strcmp (mode_str, "splines"))      st->mode = spline_mode;
+  else if (!strcmp (mode_str, "filled-splines"))st->mode = spline_filled_mode;
   else {
     fprintf (stderr,
             "%s: mode must be balls, lines, tails, polygons, splines, or\n\
@@ -138,157 +234,245 @@ init_balls (dpy, window)
     exit (1);
   }
 
-  mode_str = get_string_resource ("colorMode", "ColorMode");
-  if (! mode_str) cmode = cycle_mode;
-  else if (!strcmp (mode_str, "cycle")) cmode = cycle_mode;
-  else if (!strcmp (mode_str, "random")) cmode = random_mode;
+  graph_mode_str = get_string_resource (dpy, "graphmode", "Mode");
+  if (! graph_mode_str) st->graph_mode = graph_none;
+  else if (!strcmp (graph_mode_str, "x"))      st->graph_mode = graph_x;
+  else if (!strcmp (graph_mode_str, "y"))      st->graph_mode = graph_y;
+  else if (!strcmp (graph_mode_str, "both"))   st->graph_mode = graph_both;
+  else if (!strcmp (graph_mode_str, "speed"))  st->graph_mode = graph_speed;
+  else if (!strcmp (graph_mode_str, "none"))   st->graph_mode = graph_none;
   else {
-    fprintf (stderr, "%s: colorMode must be cycle or random, not \"%s\"\n",
-            progname, mode_str);
+    fprintf (stderr,
+        "%s: graphmode must be speed, x, y, both, or none, not \"%s\"\n",
+        progname, graph_mode_str);
     exit (1);
   }
 
-  if (mode != ball_mode && mode != tail_mode) glow_p = False;
+  /* only allocate memory if it is needed */
+  if(st->graph_mode != graph_none)
+  {
+    if(st->graph_mode == graph_x || st->graph_mode == graph_both)
+      st->x_vels = (double *) malloc (st->npoints * sizeof (double));
+    if(st->graph_mode == graph_y || st->graph_mode == graph_both)
+      st->y_vels = (double *) malloc (st->npoints * sizeof (double));
+    if(st->graph_mode == graph_speed)
+      st->speeds = (double *) malloc (st->npoints * sizeof (double));
+  }
+
+  if (st->mode != ball_mode && st->mode != tail_mode) st->glow_p = False;
   
-  if (mode == polygon_mode && npoints < 3)
-    mode = line_mode;
+  if (st->mode == polygon_mode && st->npoints < 3)
+    st->mode = line_mode;
+
+  st->ncolors = get_integer_resource (dpy, "colors", "Colors");
+  if (st->ncolors < 2) st->ncolors = 2;
+  if (st->ncolors <= 2) mono_p = True;
+  st->colors = 0;
 
-  if (mode != ball_mode)
+  if (!mono_p)
     {
-      int size = (segments ? segments : 1);
-      point_stack_size = size * (npoints + 1);
-      point_stack = (XPoint *) calloc (point_stack_size, sizeof (XPoint));
-      point_stack_fp = 0;
-      if (segments > 0)
-       pixel_stack_size = segments;
-      else
-       pixel_stack_size = (360 / color_shift);
-      pixel_stack = (unsigned long *)
-       calloc (pixel_stack_size, sizeof (unsigned int));
-      pixel_stack_fp = 0;
+      st->fg_index = 0;
+      switch (st->mode)
+       {
+       case ball_mode:
+         if (st->glow_p)
+           {
+             int H = random() % 360;
+             double S1 = 0.25;
+             double S2 = 1.00;
+             double V = frand(0.25) + 0.75;
+             st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
+             make_color_ramp (dpy, cmap, H, S1, V, H, S2, V, st->colors, &st->ncolors,
+                              False, True, False);
+           }
+         else
+           {
+             st->ncolors = st->npoints;
+             st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
+             make_random_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
+                                   True, True, False, True);
+           }
+         break;
+       case line_mode:
+       case polygon_mode:
+       case spline_mode:
+       case spline_filled_mode:
+       case tail_mode:
+         st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
+         make_smooth_colormap (dpy, xgwa.visual, cmap, st->colors, &st->ncolors,
+                               True, False, True);
+         break;
+       default:
+         abort ();
+       }
+    }
+
+  if (!mono_p && st->ncolors <= 2)
+    {
+      if (st->colors) free (st->colors);
+      st->colors = 0;
+      mono_p = True;
+    }
+
+  if (st->mode != ball_mode)
+    {
+      int size = (st->segments ? st->segments : 1);
+      st->point_stack_size = size * (st->npoints + 1);
+      st->point_stack = (XPoint *) calloc (st->point_stack_size, sizeof (XPoint));
+      st->point_stack_fp = 0;
     }
 
-  gcv.line_width = (mode == tail_mode
-                   ? (global_size ? global_size : (MAX_SIZE * 2 / 3))
+  gcv.line_width = (st->mode == tail_mode
+                   ? (st->global_size ? st->global_size : (MAX_SIZE * 2 / 3))
                    : 1);
-  gcv.cap_style = (mode == tail_mode ? CapRound : CapButt);
+  gcv.cap_style = (st->mode == tail_mode ? CapRound : CapButt);
 
-  gcv.foreground = default_fg_pixel =
-    get_pixel_resource ("foreground", "Foreground", dpy, cmap);
-  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);
+  if (mono_p)
+    gcv.foreground = get_pixel_resource(dpy, cmap, "foreground", "Foreground");
+  else
+    gcv.foreground = st->colors[st->fg_index].pixel;
+  st->draw_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle, &gcv);
+
+  gcv.foreground = get_pixel_resource(dpy, cmap, "background", "Background");
+  st->erase_gc = XCreateGC (dpy, window, GCForeground|GCLineWidth|GCCapStyle,&gcv);
 
-  if (!mono_p && mode != ball_mode)
-    for (i = 0; i < pixel_stack_size; i++)
-      {
-       XColor color;
-       color.pixel = default_fg_pixel;
-       XQueryColor (dpy, cmap, &color);
-       if (!XAllocColor (dpy, cmap, &color)) abort ();
-       pixel_stack [i] = color.pixel;
-      }
 
-#define rand_size() min (MAX_SIZE, 8 + (random () % (MAX_SIZE - 9)))
+#ifdef HAVE_COCOA
+  jwxyz_XSetAntiAliasing (dpy, st->draw_gc,  False);
+  jwxyz_XSetAntiAliasing (dpy, st->erase_gc, False);
+#endif
+
+  /* let's make the balls bigger by default */
+#define rand_size() (3 * (8 + (random () % 7)))
 
-  if (orbit_p && !global_size)
+  if (st->orbit_p && !st->global_size)
     /* To orbit, all objects must be the same mass, or the math gets
        really hairy... */
-    global_size = rand_size ();
+    st->global_size = rand_size ();
 
+ RETRY_NO_ORBIT:
   th = frand (M_PI+M_PI);
-  for (i = 0; i < npoints; i++)
+  for (i = 0; i < st->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);
-       }
-      balls [i].color.pixel = default_fg_pixel;
-      balls [i].color.flags = DoRed | DoGreen | DoBlue;
-      if (!mono_p)
+      int new_size = (st->global_size ? st->global_size : rand_size ());
+      st->balls [i].dx = 0;
+      st->balls [i].dy = 0;
+      st->balls [i].size = new_size;
+      st->balls [i].mass = (new_size * new_size * 10);
+      st->balls [i].x = midx + r * cos (i * ((M_PI+M_PI) / st->npoints) + th);
+      st->balls [i].y = midy + r * sin (i * ((M_PI+M_PI) / st->npoints) + th);
+      if (! st->orbit_p)
        {
-         if (i != 0 && (glow_p || mode != ball_mode))
-           balls [i].hue = balls [0].hue;
-         else
-           balls [i].hue = random () % 360;
-         hsv_to_rgb (balls [i].hue, 1.0, 1.0,
-                     &balls [i].color.red, &balls [i].color.green,
-                     &balls [i].color.blue);
-         if (!XAllocColor (dpy, cmap, &balls [i].color))
-           mono_p = True; /* just give up */
+         st->balls [i].vx = vx ? vx : ((6.0 - (random () % 11)) / 8.0);
+         st->balls [i].vy = vy ? vy : ((6.0 - (random () % 11)) / 8.0);
        }
+      if (mono_p || st->mode != ball_mode)
+       st->balls [i].pixel_index = -1;
+      else if (st->glow_p)
+       st->balls [i].pixel_index = 0;
+      else
+       st->balls [i].pixel_index = random() % st->ncolors;
     }
 
-  if (orbit_p)
+  /*  This lets modes where the points don't really have any size use the whole
+      window.  Otherwise, since the points still have a positive size
+      assigned to them, they will be bounced somewhat early.  Mass and size are
+      seperate, so this shouldn't cause problems.  It's a bit kludgy, tho.
+  */
+  if(st->mode == line_mode || st->mode == spline_mode || 
+     st->mode == spline_filled_mode || st->mode == polygon_mode)
+    {
+       for(i = 1; i < st->npoints; i++)
+         {
+               st->balls[i].size = 0;
+          }
+     }
+    
+  if (st->orbit_p)
     {
       double a = 0;
       double v;
-      double v_mult = get_float_resource ("vMult", "Float");
+      double v_mult = get_float_resource (dpy, "vMult", "Float");
       if (v_mult == 0.0) v_mult = 1.0;
 
-      for (i = 1; i < npoints; i++)
+      for (i = 1; i < st->npoints; i++)
        {
-          double _2ipi_n = (2 * i * M_PI / npoints);
+          double _2ipi_n = (2 * i * M_PI / st->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) *
+          double a1 = ((st->balls[i].mass / dist2) *
+                       ((dist < st->threshold) ? -1.0 : 1.0) *
                        (distx / dist));
          a += a1;
        }
       if (a < 0.0)
        {
-         fprintf (stderr, "%s: domain error: forces on balls too great\n",
+          /* "domain error: forces on balls too great" */
+         fprintf (stderr, "%s: window too small for these orbit settings.\n",
                   progname);
-         exit (-1);
+          st->orbit_p = False;
+          goto RETRY_NO_ORBIT;
        }
       v = sqrt (a * r) * v_mult;
-      for (i = 0; i < npoints; i++)
+      for (i = 0; i < st->npoints; i++)
        {
-         double k = ((2 * i * M_PI / npoints) + th);
-         balls [i].vx = -v * sin (k);
-         balls [i].vy =  v * cos (k);
+         double k = ((2 * i * M_PI / st->npoints) + th);
+         st->balls [i].vx = -v * sin (k);
+         st->balls [i].vy =  v * cos (k);
        }
     }
 
-  if (mono_p) glow_p = False;
+  if (mono_p) st->glow_p = False;
+
   XClearWindow (dpy, window);
+  return st;
 }
 
 static void
-compute_force (i, dx_ret, dy_ret)
-     int i;
-     float *dx_ret, *dy_ret;
+compute_force (struct state *st, 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++)
+  for (j = 0; j < st->npoints; j++)
     {
-      float x_dist, y_dist, dist, dist2;
-
       if (i == j) continue;
-      x_dist = balls [j].x - balls [i].x;
-      y_dist = balls [j].y - balls [i].y;
+      x_dist = st->balls [j].x - st->balls [i].x;
+      y_dist = st->balls [j].y - st->balls [i].y;
       dist2 = (x_dist * x_dist) + (y_dist * y_dist);
       dist = sqrt (dist2);
              
       if (dist > 0.1) /* the balls are not overlapping */
        {
-         float new_acc = ((balls[j].mass / dist2) *
-                          ((dist < threshold) ? -1.0 : 1.0));
-         float new_acc_dist = new_acc / dist;
+         double new_acc = ((st->balls[j].mass / dist2) *
+                           ((dist < st->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 (st->mouse_p)
+    {
+      x_dist = st->mouse_x - st->balls [i].x;
+      y_dist = st->mouse_y - st->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 = ((st->mouse_mass / dist2) *
+                           ((dist < st->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;
        }
@@ -300,253 +484,468 @@ compute_force (i, dx_ret, dy_ret)
     }
 }
 
+
+/* Draws meters along the diagonal for the x velocity */
+static void 
+draw_meter_x(Display *dpy, Window window, struct state *st, int i, int alone) 
+{
+  XWindowAttributes xgwa;
+  int x1,x2,y,w1,w2,h;
+  XGetWindowAttributes (dpy, window, &xgwa);
+
+  /* set the width of the bars to use */
+  if(xgwa.height < BAR_SIZE*st->npoints)
+    {
+      y = i*(xgwa.height/st->npoints);
+      h = (xgwa.height/st->npoints) - 2;
+    }
+  else
+    {
+      y = BAR_SIZE*i;
+      h = BAR_SIZE - 2;
+    }
+  
+  if(alone)
+    {
+      x1 = xgwa.width/2;
+      x2 = x1;
+    }
+  else
+    {
+      x1 = i*(h+2);
+      if(x1 < i) 
+        x1 = i;
+      x2 = x1;
+    }
+
+  if(y<1) y=i;  
+  if(h<1) h=1;
+
+  w1 = (int)(20*st->x_vels[i]);
+  w2 = (int)(20*st->balls[i].vx);
+  st->x_vels[i] = st->balls[i].vx; 
+
+  if (w1<0) {
+    w1=-w1;
+    x1=x1-w1;
+  }
+  if (w2<0) {
+    w2=-w2;
+    x2=x2-w2;
+  }
+  XDrawRectangle(dpy,window,st->erase_gc,x1+(h+2)/2,y,w1,h);
+  XDrawRectangle(dpy,window,st->draw_gc,x2+(h+2)/2,y,w2,h);
+}
+
+/* Draws meters along the diagonal for the y velocity.
+   Is there some way to make draw_meter_x and draw_meter_y 
+   one function instead of two without making them completely unreadable?
+*/
+static void 
+draw_meter_y (Display *dpy, Window window, struct state *st, int i, int alone) 
+{
+  XWindowAttributes xgwa;
+  int y1,y2,x,h1,h2,w;
+  XGetWindowAttributes (dpy, window, &xgwa);
+
+  if(xgwa.height < BAR_SIZE*st->npoints){  /*needs to be height still */
+    x = i*(xgwa.height/st->npoints);
+    w = (xgwa.height/st->npoints) - 2;
+  }
+  else{
+    x = BAR_SIZE*i;
+    w = BAR_SIZE - 2;
+  }
+
+  if(alone)
+    {
+      y1 = xgwa.height/2;
+      y2 = y1;
+    }
+  else
+    {
+      y1 = i*(w+2);
+      if(y1 < i)
+        y1 = i;
+      y2 = y1;
+    }
+
+  if(x < 1) x = i;  
+  if(w < 1) w = 1;
+
+  h1 = (int)(20*st->y_vels[i]);
+  h2 = (int)(20*st->balls[i].vy);
+  st->y_vels[i] = st->balls[i].vy; 
+
+  if (h1<0) {
+    h1=-h1;
+    y1=y1-h1;
+  }
+  if (h2<0) {
+    h2=-h2;
+    y2=y2-h2;
+  }
+  XDrawRectangle(dpy,window,st->erase_gc,x,y1+(w+2)/2,w,h1);
+  XDrawRectangle(dpy,window,st->draw_gc,x,y2+(w+2)/2,w,h2);
+}
+
+
+/* Draws meters of the total speed of the balls */
 static void
-run_balls (dpy, window)
-     Display *dpy;
-     Window window;
+draw_meter_speed (Display *dpy, Window window, struct state *st, int i) 
 {
-  int last_point_stack_fp = point_stack_fp;
-  static int tick = 500, xlim, ylim;
-  static Colormap cmap;
-  int i;
+  XWindowAttributes xgwa;
+  int y,x1,x2,h,w1,w2;
+  XGetWindowAttributes (dpy, window, &xgwa);
 
-  if (tick++ == 500)
+  if(xgwa.height < BAR_SIZE*st->npoints)
     {
-      XWindowAttributes xgwa;
-      XGetWindowAttributes (dpy, window, &xgwa);
-      tick = 0;
-      xlim = xgwa.width;
-      ylim = xgwa.height;
-      cmap = xgwa.colormap;
+      y = i*(xgwa.height/st->npoints);
+      h = (xgwa.height/st->npoints) - 2;
     }
+  else{
+    y = BAR_SIZE*i;
+    h = BAR_SIZE - 2;
+  }
 
-  /* compute the force of attraction/repulsion among all balls */
-  for (i = 0; i < npoints; i++)
-    compute_force (i, &balls[i].dx, &balls[i].dy);
+  x1 = 0;
+  x2 = x1;
 
-  /* move the balls according to the forces now in effect */
-  for (i = 0; i < npoints; i++)
+  if(y < 1) y = i;  
+  if(h < 1) h = 1;
+
+  w1 = (int)(5*st->speeds[i]);
+  w2 = (int)(5*(st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx));
+  st->speeds[i] =    st->balls[i].vy*st->balls[i].vy+st->balls[i].vx*st->balls[i].vx;
+
+  XDrawRectangle(dpy,window,st->erase_gc,x1,y,w1,h);
+  XDrawRectangle(dpy,window,st->draw_gc, x2,y,w2,h);
+}
+
+static unsigned long
+attraction_draw (Display *dpy, Window window, void *closure)
+{
+  struct state *st = (struct state *) closure;
+  int last_point_stack_fp = st->point_stack_fp;
+  
+  Window root1, child1;  /*flip mods for mouse interaction*/
+  unsigned int mask;
+
+  int i, radius = st->global_size/2;
+
+  st->total_ticks++;
+
+  if(st->global_size == 0)
+    radius = (MAX_SIZE / 3);
+
+  if(st->graph_mode != graph_none)
     {
-      float old_x = balls[i].x;
-      float old_y = balls[i].y;
-      float 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)
+      if(st->graph_mode == graph_both)
+       {
+          for(i = 0; i < st->npoints; i++)
+            {
+              draw_meter_x(dpy, window, st, i, 0);
+              draw_meter_y(dpy, window, st, i, 0);
+            }
+       }
+      else if(st->graph_mode == graph_x)
        {
-         balls[i].vx *= 0.9;
-         balls[i].dx = 0;
+          for(i = 0; i < st->npoints; i++)
+            {
+              draw_meter_x(dpy, window, st, i, 1);
+            }
        }
-      if (balls[i].vy > 10)
+      else if(st->graph_mode == graph_y)
        {
-         balls[i].vy *= 0.9;
-         balls[i].dy = 0;
+          for(i = 0; i < st->npoints; i++)
+            {
+              draw_meter_y(dpy, window, st, i, 1);
+            }
        }
+      else if(st->graph_mode == graph_speed)
+       {
+          for(i = 0; i < st->npoints; i++)
+            {
+              draw_meter_speed(dpy, window, st, i);
+            }
+       }
+
+    }
 
-      balls[i].x += balls[i].vx;
-      balls[i].y += balls[i].vy;
+  if (st->mouse_p)
+    {
+      XQueryPointer(dpy, window, &root1, &child1,
+                   &st->root_x, &st->root_y, &st->mouse_x, &st->mouse_y, &mask);
+    }
+
+  /* compute the force of attraction/repulsion among all balls */
+  for (i = 0; i < st->npoints; i++)
+    compute_force (st, i, &st->balls[i].dx, &st->balls[i].dy);
+
+  /* move the balls according to the forces now in effect */
+  for (i = 0; i < st->npoints; i++)
+    {
+      double old_x = st->balls[i].x;
+      double old_y = st->balls[i].y;
+      double new_x, new_y;
+      int size = st->balls[i].size;
+      st->balls[i].vx += st->balls[i].dx;
+      st->balls[i].vy += st->balls[i].dy;
 
-      /* bounce off the walls */
-      if (balls[i].x >= (xlim - balls[i].size))
+      /* "don't let them get too fast: impose a terminal velocity
+         (actually, make the medium have friction)"
+        Well, what this first step really does is give the medium a 
+        viscosity of .9 for balls going over the speed limit.  Anyway, 
+        this is now optional
+      */
+      if (fabs(st->balls[i].vx) > 10 && st->maxspeed_p)
        {
-         balls[i].x = (xlim - balls[i].size - 1);
-         if (balls[i].vx > 0)
-           balls[i].vx = -balls[i].vx;
+         st->balls[i].vx *= 0.9;
+         st->balls[i].dx = 0;
        }
-      if (balls[i].y >= (ylim - balls[i].size))
+      if (st->viscosity != 1)
        {
-         balls[i].y = (ylim - balls[i].size - 1);
-         if (balls[i].vy > 0)
-           balls[i].vy = -balls[i].vy;
+         st->balls[i].vx *= st->viscosity;
        }
-      if (balls[i].x <= 0)
+
+      if (fabs(st->balls[i].vy) > 10 && st->maxspeed_p)
        {
-         balls[i].x = 0;
-         if (balls[i].vx < 0)
-           balls[i].vx = -balls[i].vx;
+         st->balls[i].vy *= 0.9;
+         st->balls[i].dy = 0;
        }
-      if (balls[i].y <= 0)
+      if (st->viscosity != 1)
        {
-         balls[i].y = 0;
-         if (balls[i].vy < 0)
-           balls[i].vy = -balls[i].vy;
+         st->balls[i].vy *= st->viscosity;
        }
 
-      new_x = balls[i].x;
-      new_y = balls[i].y;
+      st->balls[i].x += st->balls[i].vx;
+      st->balls[i].y += st->balls[i].vy;
 
-      /* make color saturation be related to particle acceleration. */
-      if (glow_p)
+
+      /* bounce off the walls if desired
+        note: a ball is actually its upper left corner */
+      if(st->walls_p)
+       {
+         if(st->cbounce_p)  /* with correct bouncing */
+           {
+              /* so long as it's out of range, keep bouncing */
+             /* limit the maximum number to bounce to 4.*/
+             int bounce_allowed = 4;
+       
+              while( bounce_allowed && (
+                    (st->balls[i].x >= (st->xlim - st->balls[i].size)) ||
+                     (st->balls[i].y >= (st->ylim - st->balls[i].size)) ||
+                     (st->balls[i].x <= 0) ||
+                     (st->balls[i].y <= 0) )
+                    )
+                {
+                  bounce_allowed--;
+                  if (st->balls[i].x >= (st->xlim - st->balls[i].size))
+                    {
+                      st->balls[i].x = (2*(st->xlim - st->balls[i].size) - st->balls[i].x);
+                      st->balls[i].vx = -st->balls[i].vx;
+                    }
+                  if (st->balls[i].y >= (st->ylim - st->balls[i].size))
+                    {
+                      st->balls[i].y = (2*(st->ylim - st->balls[i].size) - st->balls[i].y);
+                      st->balls[i].vy = -st->balls[i].vy;
+                    }
+                  if (st->balls[i].x <= 0)
+                    {
+                      st->balls[i].x = -st->balls[i].x;
+                     st->balls[i].vx = -st->balls[i].vx;
+                    }
+                  if (st->balls[i].y <= 0)
+                    {
+                      st->balls[i].y = -st->balls[i].y;
+                     st->balls[i].vy = -st->balls[i].vy;
+                    }
+                }
+            }
+          else  /* with old bouncing */
+            {
+              if (st->balls[i].x >= (st->xlim - st->balls[i].size))
+                {
+                  st->balls[i].x = (st->xlim - st->balls[i].size - 1);
+                  if (st->balls[i].vx > 0) /* why is this check here? */
+                    st->balls[i].vx = -st->balls[i].vx;
+                }
+              if (st->balls[i].y >= (st->ylim - st->balls[i].size))
+                {
+                  st->balls[i].y = (st->ylim - st->balls[i].size - 1);
+                  if (st->balls[i].vy > 0)
+                    st->balls[i].vy = -st->balls[i].vy;
+                }
+              if (st->balls[i].x <= 0)
+                {
+                  st->balls[i].x = 0;
+                  if (st->balls[i].vx < 0)
+                    st->balls[i].vx = -st->balls[i].vx;
+                }
+              if (st->balls[i].y <= 0)
+                {
+                  st->balls[i].y = 0;
+                  if (st->balls[i].vy < 0)
+                    st->balls[i].vy = -st->balls[i].vy;
+                }
+            }
+        }
+      new_x = st->balls[i].x;
+      new_y = st->balls[i].y;
+
+      if (!mono_p)
        {
-         float limit = 0.5;
-         double s, v, fraction;
-         float vx = balls [i].dx;
-         float vy = balls [i].dy;
-         XColor new_color;
-         if (vx < 0) vx = -vx;
-         if (vy < 0) vy = -vy;
-         fraction = vx + vy;
-         if (fraction > limit) fraction = limit;
-
-         s = 1 - (fraction / limit);
-         v = 1.0;
-
-         s = (s * 0.75) + 0.25;
-
-         hsv_to_rgb (balls [i].hue, s, v, 
-                     &new_color.red, &new_color.green, &new_color.blue);
-         if (XAllocColor (dpy, cmap, &new_color))
+         if (st->mode == ball_mode)
            {
-             XFreeColors (dpy, cmap, &balls [i].color.pixel, 1, 0);
-             balls [i].color = new_color;
+             if (st->glow_p)
+               {
+                 /* make color saturation be related to particle
+                    acceleration. */
+                 double limit = 0.5;
+                 double s, fraction;
+                 double vx = st->balls [i].dx;
+                 double vy = st->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);
+                 st->balls[i].pixel_index = (st->ncolors * s);
+               }
+             XSetForeground (dpy, st->draw_gc,
+                             st->colors[st->balls[i].pixel_index].pixel);
            }
        }
 
-      if (mode == ball_mode)
+      if (st->mode == ball_mode)
        {
-         if (!mono_p)
-           XSetForeground (dpy, draw_gc, balls [i].color.pixel);
-         XFillArc (dpy, window, erase_gc, (int) old_x, (int) old_y,
+         XFillArc (dpy, window, st->erase_gc, (int) old_x, (int) old_y,
                    size, size, 0, 360*64);
-         XFillArc (dpy, window, draw_gc,  (int) new_x, (int) new_y,
+         XFillArc (dpy, window, st->draw_gc,  (int) new_x, (int) new_y,
                    size, size, 0, 360*64);
        }
-      if (mode != ball_mode)
+      else
        {
-         point_stack [point_stack_fp].x = new_x;
-         point_stack [point_stack_fp].y = new_y;
-         point_stack_fp++;
+         st->point_stack [st->point_stack_fp].x = new_x;
+         st->point_stack [st->point_stack_fp].y = new_y;
+         st->point_stack_fp++;
        }
     }
 
   /* draw the lines or polygons after computing all points */
-  if (mode != ball_mode)
+  if (st->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 */
+      st->point_stack [st->point_stack_fp].x = st->balls [0].x; /* close the polygon */
+      st->point_stack [st->point_stack_fp].y = st->balls [0].y;
+      st->point_stack_fp++;
+      if (st->point_stack_fp == st->point_stack_size)
+       st->point_stack_fp = 0;
+      else if (st->point_stack_fp > st->point_stack_size) /* better be aligned */
        abort ();
       if (!mono_p)
        {
-         XColor color2;
-         color2 = balls [0].color;
-         switch (cmode)
-           {
-           case cycle_mode:
-             cycle_hue (&color2, color_shift);
-             break;
-           case random_mode:
-             color2.red =   random () % 65535;
-             color2.green = random () % 65535;
-             color2.blue =  random () % 65535;
-             break;
-           default:
-             abort ();
-           }
-             
-         if (!XAllocColor (dpy, cmap, &color2))
+         if (st->color_tick++ == st->color_shift)
            {
-             color2 = balls [0].color;
-             if (!XAllocColor (dpy, cmap, &balls [0].color))
-               abort ();
+             st->color_tick = 0;
+             st->fg_index = (st->fg_index + 1) % st->ncolors;
+             XSetForeground (dpy, st->draw_gc, st->colors[st->fg_index].pixel);
            }
-         pixel_stack [pixel_stack_fp++] = balls [0].color.pixel;
-         if (pixel_stack_fp >= pixel_stack_size)
-           pixel_stack_fp = 0;
-         XFreeColors (dpy, cmap, pixel_stack + pixel_stack_fp, 1, 0);
-         balls [0].color = color2;
-         XSetForeground (dpy, draw_gc, balls [0].color.pixel);
        }
     }
 
-  switch (mode)
+  switch (st->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);
+      if (st->segments > 0)
+       XDrawLines (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
+                   st->npoints + 1, CoordModeOrigin);
+      XDrawLines (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
+                 st->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),
+      if (st->segments > 0)
+       XFillPolygon (dpy, window, st->erase_gc, st->point_stack + st->point_stack_fp,
+                     st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
                      CoordModeOrigin);
-      XFillPolygon (dpy, window, draw_gc, point_stack + last_point_stack_fp,
-                   npoints + 1, (npoints == 3 ? Convex : Complex),
+      XFillPolygon (dpy, window, st->draw_gc, st->point_stack + last_point_stack_fp,
+                   st->npoints + 1, (st->npoints == 3 ? Convex : Complex),
                    CoordModeOrigin);
       break;
     case tail_mode:
       {
-       int i;
-       for (i = 0; i < npoints; i++)
+       for (i = 0; i < st->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);
-
+           int index = st->point_stack_fp + i;
+           int next_index = (index + (st->npoints + 1)) % st->point_stack_size;
+            if(st->no_erase_yet == 1)
+             {
+               if(st->total_ticks >= st->segments)
+                  {
+                    st->no_erase_yet = 0;
+                    XDrawLine (dpy, window, st->erase_gc,
+                              st->point_stack [index].x + radius,
+                              st->point_stack [index].y + radius,
+                              st->point_stack [next_index].x + radius,
+                              st->point_stack [next_index].y + radius);
+                  }
+             }
+           else
+             {
+               XDrawLine (dpy, window, st->erase_gc,
+                           st->point_stack [index].x + radius,
+                           st->point_stack [index].y + radius,
+                           st->point_stack [next_index].x + radius,
+                           st->point_stack [next_index].y + radius);
+             }
            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)
+           next_index = (index - (st->npoints + 1)) % st->point_stack_size;
+           if (next_index < 0) next_index += st->point_stack_size;
+           if (st->point_stack [next_index].x == 0 &&
+               st->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);
+           XDrawLine (dpy, window, st->draw_gc,
+                      st->point_stack [index].x + radius,
+                      st->point_stack [index].y + radius,
+                      st->point_stack [next_index].x + radius,
+                      st->point_stack [next_index].y + radius);
          }
       }
       break;
     case spline_mode:
     case spline_filled_mode:
       {
-       int i;
-       static spline *s = 0;
-       if (! s) s = make_spline (npoints);
-       if (segments > 0)
+       if (! st->spl) st->spl = make_spline (st->npoints);
+       if (st->segments > 0)
          {
-           for (i = 0; i < npoints; i++)
+           for (i = 0; i < st->npoints; i++)
              {
-               s->control_x [i] = point_stack [point_stack_fp + i].x;
-               s->control_y [i] = point_stack [point_stack_fp + i].y;
+               st->spl->control_x [i] = st->point_stack [st->point_stack_fp + i].x;
+               st->spl->control_y [i] = st->point_stack [st->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),
+           compute_closed_spline (st->spl);
+           if (st->mode == spline_filled_mode)
+             XFillPolygon (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
+                           (st->spl->n_points == 3 ? Convex : Complex),
                            CoordModeOrigin);
            else
-             XDrawLines (dpy, window, erase_gc, s->points, s->n_points,
+             XDrawLines (dpy, window, st->erase_gc, st->spl->points, st->spl->n_points,
                          CoordModeOrigin);
          }
-       for (i = 0; i < npoints; i++)
+       for (i = 0; i < st->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;
+           st->spl->control_x [i] = st->point_stack [last_point_stack_fp + i].x;
+           st->spl->control_y [i] = st->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),
+       compute_closed_spline (st->spl);
+       if (st->mode == spline_filled_mode)
+         XFillPolygon (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
+                       (st->spl->n_points == 3 ? Convex : Complex),
                        CoordModeOrigin);
        else
-         XDrawLines (dpy, window, draw_gc, s->points, s->n_points,
+         XDrawLines (dpy, window, st->draw_gc, st->spl->points, st->spl->n_points,
                      CoordModeOrigin);
       }
       break;
@@ -554,55 +953,97 @@ run_balls (dpy, window)
       abort ();
     }
 
-  XSync (dpy, True);
+  return st->delay;
 }
 
-\f
-char *progclass = "Attraction";
+static void
+attraction_reshape (Display *dpy, Window window, void *closure, 
+                    unsigned int w, unsigned int h)
+{
+  struct state *st = (struct state *) closure;
+  st->xlim = w;
+  st->ylim = h;
+}
+
+static Bool
+attraction_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+  return False;
+}
 
-char *defaults [] = {
-  "*background:        black",
-  "*foreground:        white",
+static void
+attraction_free (Display *dpy, Window window, void *closure)
+{
+  struct state *st = (struct state *) closure;
+
+  if (st->balls)       free (st->balls);
+  if (st->x_vels)      free (st->x_vels);
+  if (st->y_vels)      free (st->y_vels);
+  if (st->speeds)      free (st->speeds);
+  if (st->point_stack) free (st->point_stack);
+  if (st->colors)      free (st->colors);
+  if (st->spl)         free_spline (st->spl);
+
+  free (st);
+}
+
+\f
+static const char *attraction_defaults [] = {
+  ".background:        black",
+  ".foreground:        white",
   "*mode:      balls",
+  "*graphmode:  none",
   "*points:    0",
   "*size:      0",
-  "*threshold: 100",
+  "*colors:    200",
+  "*threshold: 200",
   "*delay:     10000",
   "*glow:      false",
+  "*mouseSize: 10",
+  "*walls:     true",
+  "*maxspeed:  true",
+  "*cbounce:   true",
+  "*mouse:     false",
+  "*viscosity: 1",
   "*orbit:     false",
   "*colorShift:        3",
-  "*segments:  100",
+  "*segments:  500",
+  "*vMult:     0.9",
+  "*radius:    0",
+  "*vx:                0",
+  "*vy:                0",
   0
 };
 
-XrmOptionDescRec options [] = {
+static XrmOptionDescRec attraction_options [] = {
   { "-mode",           ".mode",        XrmoptionSepArg, 0 },
+  { "-graphmode",      ".graphmode",   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 },
-  { "-color-mode",     ".colorMode",   XrmoptionSepArg, 0 },
-  { "-color-shift",    ".colorShift",  XrmoptionSepArg, 0 },
   { "-radius",         ".radius",      XrmoptionSepArg, 0 },
   { "-vx",             ".vx",          XrmoptionSepArg, 0 },
   { "-vy",             ".vy",          XrmoptionSepArg, 0 },
   { "-vmult",          ".vMult",       XrmoptionSepArg, 0 },
+  { "-mouse-size",     ".mouseSize",   XrmoptionSepArg, 0 },
+  { "-viscosity",      ".viscosity",   XrmoptionSepArg, 0 },
+  { "-mouse",          ".mouse",       XrmoptionNoArg, "true" },
+  { "-nomouse",                ".mouse",       XrmoptionNoArg, "false" },
   { "-glow",           ".glow",        XrmoptionNoArg, "true" },
   { "-noglow",         ".glow",        XrmoptionNoArg, "false" },
-  { "-orbit",          ".orbit",       XrmoptionNoArg, "true" }
+  { "-orbit",          ".orbit",       XrmoptionNoArg, "true" },
+  { "-nowalls",                ".walls",       XrmoptionNoArg, "false" },
+  { "-walls",          ".walls",       XrmoptionNoArg, "true" },
+  { "-nomaxspeed",     ".maxspeed",    XrmoptionNoArg, "false" },
+  { "-maxspeed",       ".maxspeed",    XrmoptionNoArg, "true" },
+  { "-correct-bounce", ".cbounce",     XrmoptionNoArg, "false" },
+  { "-fast-bounce",    ".cbounce",     XrmoptionNoArg, "true" },
+  { 0, 0, 0, 0 }
 };
-int options_size = (sizeof (options) / sizeof (options[0]));
 
-void
-screenhack (dpy, window)
-     Display *dpy;
-     Window window;
-{
-  init_balls (dpy, window);
-  while (1)
-    {
-      run_balls (dpy, window);
-      if (delay) usleep (delay);
-    }
-}
+
+XSCREENSAVER_MODULE ("Attraction", attraction)