From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / fluidballs.c
index b60e4395788b756ff44596461c4010cb83d0d1ff..6bab228740d4c38266dfa2fec6fe9b2691c60fbd 100644 (file)
  * Ported to X11 and xscreensaver by jwz, 27-Feb-2002.
  *
  * http://astronomy.swin.edu.au/~pbourke/modelling/fluid/
+ *
+ * Some physics improvements by Steven Barker <steve@blckknght.org>
  */
 
-/* cjb notes
- *
- * Future ideas:
+/* Future ideas:
  * Specifying a distribution in the ball sizes (with a gamma curve, possibly).
  * Brownian motion, for that extra touch of realism.
+ *
+ * It would be nice to detect when there are more balls than fit in
+ * the window, and scale the number of balls back.  Useful for the
+ * xscreensaver-demo preview, which is often too tight by default.
  */
 
 #include <math.h>
@@ -32,10 +36,12 @@ typedef struct {
   Display *dpy;
   Window window;
   XWindowAttributes xgwa;
+  int delay;
 
   Pixmap b, ba;        /* double-buffer to reduce flicker */
 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
   XdbeBackBuffer backb;
+  Bool dbeclear_p;
 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
 
   GC draw_gc;          /* most of the balls */
@@ -60,13 +66,12 @@ typedef struct {
   float *r;            /* ball radiuses */
 
   float *m;            /* ball mass, precalculated */
-  float e;             /* coefficient of friction, I think? */
+  float e;             /* coeficient of elasticity */
   float max_radius;    /* largest radius of any ball */
 
   Bool random_sizes_p;  /* Whether balls should be various sizes up to max. */
   Bool shake_p;                /* Whether to mess with gravity when things settle. */
   Bool dbuf;            /* Whether we're using double buffering. */
-  Bool dbeclear_p;      /* ? */
   float shake_threshold;
   int time_since_shake;
 
@@ -78,6 +83,9 @@ typedef struct {
   int collision_count;
   char fps_str[1024];
   
+  int time_tick;
+  struct timeval last_time;
+
 } b_state;
 
 
@@ -86,10 +94,11 @@ static void
 draw_fps_string (b_state *state)
 {  
   XFillRectangle (state->dpy, state->b, state->erase_gc,
-                 0, state->xgwa.height - state->font_height,
-                 state->xgwa.width, state->font_height);
+                 0, state->xgwa.height - state->font_height*3 - 20,
+                 state->xgwa.width, state->font_height*3 + 20);
   XDrawImageString (state->dpy, state->b, state->font_gc,
-                   0, state->xgwa.height - state->font_baseline,
+                   10, state->xgwa.height - state->font_height*2 -
+                    state->font_baseline - 10,
                    state->fps_str, strlen(state->fps_str));
 }
 
@@ -99,32 +108,8 @@ draw_fps_string (b_state *state)
 static void
 window_origin (Display *dpy, Window window, int *x, int *y)
 {
-  Window root, parent, *kids;
-  unsigned int nkids;
-  XWindowAttributes xgwa;
-  int wx, wy;
-  XGetWindowAttributes (dpy, window, &xgwa);
-
-  wx = xgwa.x;
-  wy = xgwa.y;
-
-  kids = 0;
-  *x = 0;
-  *y = 0;
-
-  if (XQueryTree (dpy, window, &root, &parent, &kids, &nkids))
-    {
-      if (parent && parent != root)
-        {
-          int px, py;
-          window_origin (dpy, parent, &px, &py);
-          wx += px;
-          wy += py;
-        }
-    }
-  if (kids) XFree (kids);
-  *x = wx;
-  *y = wy;
+  XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen (dpy)),
+                         0, 0, x, y, &window);
 }
 
 
@@ -148,7 +133,8 @@ check_window_moved (b_state *state)
   state->xmin = wx;
   state->ymin = wy;
   state->xmax = state->xmin + state->xgwa.width;
-  state->ymax = state->ymin + state->xgwa.height - state->font_height;
+  state->ymax = state->ymin + state->xgwa.height - (state->font_height*3) -
+    (state->font_height ? 22 : 0);
 
   if (state->dbuf && (state->ba))
     {
@@ -170,8 +156,8 @@ check_window_moved (b_state *state)
        XClearWindow (state->dpy, state->window);
       else if (state->fps_p && oymax != state->ymax)
        XFillRectangle (state->dpy, state->b, state->erase_gc,
-                       0, state->xgwa.height - state->font_height,
-                       state->xgwa.width, state->font_height);
+                       0, state->xgwa.height - state->font_height*3,
+                       state->xgwa.width, state->font_height*3);
     }
 }
 
@@ -226,8 +212,8 @@ recolor (b_state *state)
 
 /* Initialize the state structure and various X data.
  */
-static b_state *
-init_balls (Display *dpy, Window window)
+static void *
+fluidballs_init (Display *dpy, Window window)
 {
   int i;
   float extx, exty;
@@ -235,17 +221,21 @@ init_balls (Display *dpy, Window window)
   XGCValues gcv;
 
   state->dpy = dpy;
-
   state->window = window;
+  state->delay = get_integer_resource (dpy, "delay", "Integer");
 
   check_window_moved (state);
 
-  state->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
-  state->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
+  state->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
+
+# ifdef HAVE_COCOA     /* Don't second-guess Quartz's double-buffering */
+  state->dbuf = False;
+# endif
 
   if (state->dbuf)
     {
 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+      state->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
       if (state->dbeclear_p)
         state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
       else
@@ -268,19 +258,21 @@ init_balls (Display *dpy, Window window)
 
   /* Select ButtonRelease events on the external window, if no other app has
      already selected it (only one app can select it at a time: BadAccess. */
+#if 0
   if (! (state->xgwa.all_event_masks & ButtonReleaseMask))
     XSelectInput (state->dpy, state->window,
                   state->xgwa.your_event_mask | ButtonReleaseMask);
+#endif
 
-  gcv.foreground = get_pixel_resource("foreground", "Foreground",
-                                      state->dpy, state->xgwa.colormap);
-  gcv.background = get_pixel_resource("background", "Background",
-                                      state->dpy, state->xgwa.colormap);
+  gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
+                                      "foreground", "Foreground");
+  gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
+                                      "background", "Background");
   state->draw_gc = XCreateGC (state->dpy, state->b,
                               GCForeground|GCBackground, &gcv);
 
-  gcv.foreground = get_pixel_resource("mouseForeground", "MouseForeground",
-                                      state->dpy, state->xgwa.colormap);
+  gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
+                                      "mouseForeground", "MouseForeground");
   state->draw_gc2 = XCreateGC (state->dpy, state->b,
                                GCForeground|GCBackground, &gcv);
 
@@ -298,43 +290,63 @@ init_balls (Display *dpy, Window window)
   extx = state->xmax - state->xmin;
   exty = state->ymax - state->ymin;
 
-  state->count = get_integer_resource ("count", "Count");
+  state->count = get_integer_resource (dpy, "count", "Count");
   if (state->count < 1) state->count = 20;
 
-  state->max_radius = get_float_resource ("size", "Size") / 2;
+  state->max_radius = get_float_resource (dpy, "size", "Size") / 2;
   if (state->max_radius < 1.0) state->max_radius = 1.0;
 
-  state->random_sizes_p = get_boolean_resource ("random", "Random");
+  state->random_sizes_p = get_boolean_resource (dpy, "random", "Random");
 
-  state->accx = get_float_resource ("wind", "Wind");
+  /* If the initial window size is too small to hold all these balls,
+     make fewer of them...
+   */
+  {
+    float r = (state->random_sizes_p
+               ? state->max_radius * 0.7
+               : state->max_radius);
+    float ball_area = M_PI * r * r;
+    float balls_area = state->count * ball_area;
+    float window_area = state->xgwa.width * state->xgwa.height;
+    window_area *= 0.75;  /* don't pack it completely full */
+    if (balls_area > window_area)
+      state->count = window_area / ball_area;
+  }
+
+  state->accx = get_float_resource (dpy, "wind", "Wind");
   if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0;
 
-  state->accy = get_float_resource ("gravity", "Gravity");
+  state->accy = get_float_resource (dpy, "gravity", "Gravity");
   if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01;
 
-  state->e = get_float_resource ("friction", "Friction");
+  state->e = get_float_resource (dpy, "elasticity", "Elacitcity");
   if (state->e < 0.2 || state->e > 1.0) state->e = 0.97;
 
-  state->tc = get_float_resource ("timeScale", "TimeScale");
+  state->tc = get_float_resource (dpy, "timeScale", "TimeScale");
   if (state->tc <= 0 || state->tc > 10) state->tc = 1.0;
 
-  state->shake_p = get_boolean_resource ("shake", "Shake");
-  state->shake_threshold = get_float_resource ("shakeThreshold",
+  state->shake_p = get_boolean_resource (dpy, "shake", "Shake");
+  state->shake_threshold = get_float_resource (dpy, "shakeThreshold",
                                                "ShakeThreshold");
+  state->time_tick = 999999;
 
-  state->fps_p = get_boolean_resource ("doFPS", "DoFPS");
+# ifdef USE_IPHONE     /* Always obey real-world gravity */
+  state->shake_p = False;
+# endif
+
+
+  state->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
   if (state->fps_p)
     {
       XFontStruct *font;
-      char *fontname = get_string_resource ("font", "Font");
-      const char *def_font = "fixed";
-      if (!fontname || !*fontname) fontname = (char *)def_font;
+      char *fontname = get_string_resource (dpy, "fpsFont", "Font");
+      if (!fontname) fontname = "-*-courier-bold-r-normal-*-180-*";
       font = XLoadQueryFont (dpy, fontname);
-      if (!font) font = XLoadQueryFont (dpy, def_font);
+      if (!font) font = XLoadQueryFont (dpy, "fixed");
       if (!font) exit(-1);
       gcv.font = font->fid;
-      gcv.foreground = get_pixel_resource("textColor", "Foreground",
-                                          state->dpy, state->xgwa.colormap);
+      gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
+                                          "textColor", "Foreground");
       state->font_gc = XCreateGC(dpy, state->b,
                                  GCFont|GCForeground|GCBackground, &gcv);
       state->font_height = font->ascent + font->descent;
@@ -366,6 +378,9 @@ init_balls (Display *dpy, Window window)
       state->m[i] = pow(state->r[i],3) * M_PI * 1.3333;
     }
 
+  memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1));
+  memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1));
+
   return state;
 }
 
@@ -413,13 +428,11 @@ shake (b_state *state)
 static void
 check_wall_clock (b_state *state, float max_d)
 {
-  static int tick = 0;
   state->frame_count++;
   
-  if (tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
+  if (state->time_tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
     {
       struct timeval now;
-      static struct timeval last = {0, };
 # ifdef GETTIMEOFDAY_TWO_ARGS
       struct timezone tzp;
       gettimeofday(&now, &tzp);
@@ -427,32 +440,45 @@ check_wall_clock (b_state *state, float max_d)
       gettimeofday(&now);
 # endif
 
-      if (last.tv_sec == 0)
-        last = now;
+      if (state->last_time.tv_sec == 0)
+        state->last_time = now;
 
-      tick = 0;
-      if (now.tv_sec == last.tv_sec)
+      state->time_tick = 0;
+      if (now.tv_sec == state->last_time.tv_sec)
         return;
 
-      state->time_since_shake += (now.tv_sec - last.tv_sec);
+      state->time_since_shake += (now.tv_sec - state->last_time.tv_sec);
+
+# ifdef USE_IPHONE     /* Always obey real-world gravity */
+      {
+        float a = fabs (fabs(state->accx) > fabs(state->accy)
+                        ? state->accx : state->accy);
+        switch ((int) current_device_rotation ()) {
+        case    0: case  360: state->accx =  0; state->accy =  a; break;
+        case  -90:            state->accx = -a; state->accy =  0; break;
+        case   90:            state->accx =  a; state->accy =  0; break;
+        case  180: case -180: state->accx =  0; state->accy = -a; break;
+        default: break;
+        }
+      }
+# endif /* USE_IPHONE */
 
       if (state->fps_p) 
        {
          float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
-                          (last.tv_sec + (last.tv_usec / 1000000.0)));
+                          (state->last_time.tv_sec + (state->last_time.tv_usec / 1000000.0)));
          float fps = state->frame_count / elapsed;
          float cps = state->collision_count / elapsed;
          
-         sprintf (state->fps_str, 
-                  " FPS: %.2f  Collisions: %.3f/frame  Max motion: %.3f",
-                  fps, cps/fps, max_d);
+         sprintf (state->fps_str, "Collisions: %.3f/frame  Max motion: %.3f",
+                  cps/fps, max_d);
          
          draw_fps_string(state);
        }
 
       state->frame_count = 0;
       state->collision_count = 0;
-      last = now;
+      state->last_time = now;
     }
 }
 
@@ -466,6 +492,10 @@ repaint_balls (b_state *state)
   int x1b, x2b, y1b, y2b;
   float max_d = 0;
 
+#ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
+  XClearWindow (state->dpy, state->b);
+#endif
+
   for (a=1; a <= state->count; a++)
     {
       GC gc;
@@ -479,11 +509,10 @@ repaint_balls (b_state *state)
       x2b = (state->px[a] + state->r[a] - state->xmin);
       y2b = (state->py[a] + state->r[a] - state->ymin);
 
-      if (!state->dbeclear_p ||
+#ifndef HAVE_COCOA     /* Don't second-guess Quartz's double-buffering */
 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
-          !state->backb
+      if (!state->dbeclear_p || !state->backb)
 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
-         )
        {
 /*       if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
            {
@@ -493,6 +522,8 @@ repaint_balls (b_state *state)
                        0, 360*64);
            }
        }
+#endif /* !HAVE_COCOA */
+
       if (state->mouse_ball == a)
         gc = state->draw_gc2;
       else
@@ -516,7 +547,11 @@ repaint_balls (b_state *state)
       state->opy[a] = state->py[a];
     }
 
-  if (state->fps_p && state->dbeclear_p) 
+  if (state->fps_p
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+      && (state->backb ? state->dbeclear_p : 1)
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+      )
     draw_fps_string(state);
 
 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
@@ -556,7 +591,7 @@ update_balls (b_state *state)
 {
   int a, b;
   float d, vxa, vya, vxb, vyb, dd, cdx, cdy;
-  float ma, mb, vela, velb, vela1, velb1;
+  float ma, mb, vca, vcb, dva, dvb;
   float dee2;
 
   check_window_moved (state);
@@ -580,71 +615,69 @@ update_balls (b_state *state)
     }
 
   /* For each ball, compute the influence of every other ball. */
-  for (a=1; a <= state->count; a++)
-    if (a != state->mouse_ball)
-      for (b=1; b <= state->count; b++)
-        if (a != b)
-          {
-            d = ((state->px[a] - state->px[b]) *
-                 (state->px[a] - state->px[b]) +
-                 (state->py[a] - state->py[b]) *
-                 (state->py[a] - state->py[b]));
-           dee2 = (state->r[a] + state->r[b]) *
-                  (state->r[a] + state->r[b]);
-            if (d < dee2)
-              {
-                state->collision_count++;
-                d = sqrt(d);
-               dd = state->r[a] + state->r[b] - d;
-               /* A pair of balls that have already collided in this
-                * current frame (and therefore touching each other)
-                * should not have another collision calculated, hence
-                * the fallthru if "dd ~= 0.0".
-                */
-               if ((dd < -0.01) || (dd > 0.01))
-                 {
-                    cdx = (state->px[b] - state->px[a]) / d;
-                    cdy = (state->py[b] - state->py[a]) / d;
-
-                   /* Move each ball apart from the other by half the
-                    * 'collision' distance.
-                    */
-                    state->px[a] -= 0.5 * dd * cdx;
-                    state->py[a] -= 0.5 * dd * cdy;
-                    state->px[b] += 0.5 * dd * cdx;
-                    state->py[b] += 0.5 * dd * cdy;
-
-                   ma = state->m[a];
-                   mb = state->m[b];
-                   vxa = state->vx[a];
-                   vya = state->vy[a];
-                   vxb = state->vx[b];
-                   vyb = state->vy[b];
-
-                   vela = sqrt((vxa * vxa) + (vya * vya));
-                   velb = sqrt((vxb * vxb) + (vyb * vyb));
-
-                   vela1 = vela * ((ma - mb) / (ma + mb)) +
-                           velb * ((2 * mb) / (ma + mb));
-                   velb1 = vela * ((2 * ma) / (ma + mb)) +
-                           velb * ((mb - ma) / (ma + mb));
-
-                   vela1 *= state->e; /* "air resistance" */
-                   velb1 *= state->e;
+  for (a=1; a <= state->count -  1; a++)
+    for (b=a + 1; b <= state->count; b++)
+      {
+         d = ((state->px[a] - state->px[b]) *
+              (state->px[a] - state->px[b]) +
+              (state->py[a] - state->py[b]) *
+              (state->py[a] - state->py[b]));
+         dee2 = (state->r[a] + state->r[b]) *
+                (state->r[a] + state->r[b]);
+         if (d < dee2)
+         {
+            state->collision_count++;
+            d = sqrt(d);
+            dd = state->r[a] + state->r[b] - d;
+
+            cdx = (state->px[b] - state->px[a]) / d;
+            cdy = (state->py[b] - state->py[a]) / d;
+
+            /* Move each ball apart from the other by half the
+             * 'collision' distance.
+             */
+            state->px[a] -= 0.5 * dd * cdx;
+            state->py[a] -= 0.5 * dd * cdy;
+            state->px[b] += 0.5 * dd * cdx;
+            state->py[b] += 0.5 * dd * cdy;
+
+            ma = state->m[a];
+            mb = state->m[b];
+
+            vxa = state->vx[a];
+            vya = state->vy[a];
+            vxb = state->vx[b];
+            vyb = state->vy[b];
+
+            vca = vxa * cdx + vya * cdy; /* the component of each velocity */
+            vcb = vxb * cdx + vyb * cdy; /* along the axis of the collision */
+
+            /* elastic collison */
+            dva = (vca * (ma - mb) + vcb * 2 * mb) / (ma + mb) - vca;
+            dvb = (vcb * (mb - ma) + vca * 2 * ma) / (ma + mb) - vcb;
+
+            dva *= state->e; /* some energy lost to inelasticity */
+            dvb *= state->e;
+
 #if 0
-                   vela1 += (frand (50) - 25) / ma; /* brownian motion */
-                   velb1 += (frand (50) - 25) / mb;
+            dva += (frand (50) - 25) / ma;   /* q: why are elves so chaotic? */
+            dvb += (frand (50) - 25) / mb;   /* a: brownian motion. */
 #endif
-                   state->vx[a] = -cdx * vela1;
-                   state->vy[a] = -cdy * vela1;
-                   state->vx[b] = cdx * velb1;
-                   state->vy[b] = cdy * velb1;
-                  }
-              }
-          }
 
-  /* Force all balls to be on screen.
-   */
+            vxa += dva * cdx;
+            vya += dva * cdy;
+            vxb += dvb * cdx;
+            vyb += dvb * cdy;
+
+            state->vx[a] = vxa;
+            state->vy[a] = vya;
+            state->vx[b] = vxb;
+            state->vy[b] = vyb;
+         }
+      }
+
+   /* Force all balls to be on screen.
+    */
   for (a=1; a <= state->count; a++)
     {
       if (state->px[a] <= (state->xmin + state->r[a]))
@@ -684,68 +717,94 @@ update_balls (b_state *state)
 
 /* Handle X events, specifically, allow a ball to be picked up with the mouse.
  */
-static void
-handle_events (b_state *state)
+static Bool
+fluidballs_event (Display *dpy, Window window, void *closure, XEvent *event)
 {
-  XSync (state->dpy, False);
-  while (XPending (state->dpy))
+  b_state *state = (b_state *) closure;
+
+  if (event->xany.type == ButtonPress)
     {
-      XEvent event;
-      XNextEvent (state->dpy, &event);
-      if (event.xany.type == ButtonPress)
+      int i, rx, ry;
+      XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen(dpy)),
+                             event->xbutton.x, event->xbutton.y, &rx, &ry,
+                             &window);
+
+      if (state->mouse_ball != 0)  /* second down-click?  drop the ball. */
+        {
+          state->mouse_ball = 0;
+          return True;
+        }
+      else
         {
-          int i;
-          float fmx = event.xbutton.x_root;
-          float fmy = event.xbutton.y_root;
-          if (state->mouse_ball != 0)  /* second down-click?  drop the ball. */
-            {
-              state->mouse_ball = 0;
-              return;
-            }
-          else
-            for (i=1; i <= state->count; i++)
+          /* When trying to pick up a ball, first look for a click directly
+             inside the ball; but if we don't find it, expand the radius
+             outward until we find something nearby.
+           */
+          float max = state->max_radius * 4;
+          float step = max / 10;
+          float r2;
+          for (r2 = step; r2 < max; r2 += step) {
+            for (i = 1; i <= state->count; i++)
               {
-                float d = ((state->px[i] - fmx) * (state->px[i] - fmx) +
-                           (state->py[i] - fmy) * (state->py[i] - fmy));
+                float d = ((state->px[i] - rx) * (state->px[i] - rx) +
+                           (state->py[i] - ry) * (state->py[i] - ry));
                 float r = state->r[i];
+                if (r2 > r) r = r2;
                 if (d < r*r)
                   {
                     state->mouse_ball = i;
-                    return;
+                    return True;
                   }
               }
+          }
         }
-      else if (event.xany.type == ButtonRelease)   /* drop the ball */
-        {
-          state->mouse_ball = 0;
-          return;
-        }
-      else if (event.xany.type == ConfigureNotify)
-        {
-          /* This is redundant, since we poll this every iteration. */
-          check_window_moved (state);
-        }
-
-      screenhack_handle_event (state->dpy, &event);
+      return True;
     }
+  else if (event->xany.type == ButtonRelease)   /* drop the ball */
+    {
+      state->mouse_ball = 0;
+      return True;
+    }
+
+  return False;
+}
+
+static unsigned long
+fluidballs_draw (Display *dpy, Window window, void *closure)
+{
+  b_state *state = (b_state *) closure;
+  repaint_balls(state);
+  update_balls(state);
+  return state->delay;
 }
 
-\f
-char *progclass = "FluidBalls";
+static void
+fluidballs_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
+{
+}
 
-char *defaults [] = {
+static void
+fluidballs_free (Display *dpy, Window window, void *closure)
+{
+  b_state *state = (b_state *) closure;
+  free (state);
+}
+
+
+static const char *fluidballs_defaults [] = {
   ".background:                black",
-  ".textColor:         yellow",
-  ".font:              -*-helvetica-*-r-*-*-*-180-*-*-p-*-*-*",
+  ".foreground:                yellow",
+  ".textColor:         white",
+  "*mouseForeground:   white",
   "*delay:             10000",
   "*count:             300",
   "*size:              25",
   "*random:            True",
   "*gravity:           0.01",
   "*wind:              0.00",
-  "*friction:          0.8",
+  "*elasticity:                0.97",
   "*timeScale:         1.0",
-  "*doFPS:             False",
   "*shake:             True",
   "*shakeThreshold:    0.015",
   "*doubleBuffer:      True",
@@ -753,41 +812,29 @@ char *defaults [] = {
   "*useDBE:            True",
   "*useDBEClear:       True",
 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+#ifdef USE_IPHONE
+  "*ignoreRotation:    True",
+#endif
   0
 };
 
-XrmOptionDescRec options [] = {
+static XrmOptionDescRec fluidballs_options [] = {
   { "-delay",          ".delay",       XrmoptionSepArg, 0 },
   { "-count",          ".count",       XrmoptionSepArg, 0 },
   { "-size",           ".size",        XrmoptionSepArg, 0 },
   { "-count",          ".count",       XrmoptionSepArg, 0 },
   { "-gravity",                ".gravity",     XrmoptionSepArg, 0 },
   { "-wind",           ".wind",        XrmoptionSepArg, 0 },
-  { "-friction",       ".friction",    XrmoptionSepArg, 0 },
-  { "-fps",            ".doFPS",       XrmoptionNoArg, "True" },
-  { "-no-fps",         ".doFPS",       XrmoptionNoArg, "False" },
+  { "-elasticity",     ".elasticity",  XrmoptionSepArg, 0 },
   { "-shake",          ".shake",       XrmoptionNoArg, "True" },
   { "-no-shake",       ".shake",       XrmoptionNoArg, "False" },
   { "-random",         ".random",      XrmoptionNoArg, "True" },
+  { "-no-random",      ".random",      XrmoptionNoArg, "False" },
   { "-nonrandom",      ".random",      XrmoptionNoArg, "False" },
   { "-db",             ".doubleBuffer", XrmoptionNoArg,  "True" },
   { "-no-db",          ".doubleBuffer", XrmoptionNoArg,  "False" },
   { 0, 0, 0, 0 }
 };
 
-void
-screenhack (Display *dpy, Window window)
-{
-  b_state *state = init_balls(dpy, window);
-  int delay = get_integer_resource ("delay", "Integer");
 
-  while (1)
-    {
-      repaint_balls(state);
-      update_balls(state);
-
-      XSync (dpy, False);
-      handle_events (state);
-      if (delay) usleep (delay);
-    }
-}
+XSCREENSAVER_MODULE ("FluidBalls", fluidballs)