ftp://netsw.org/x11/tools/desktop/xscreensaver-4.07.tar.gz
[xscreensaver] / hacks / fluidballs.c
index 528c9994d4341617e261f9fe323120af8ccf3943..b60e4395788b756ff44596461c4010cb83d0d1ff 100644 (file)
@@ -5,7 +5,7 @@
  * 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 
+ * software for any purpose.  It is provided "as is" without express or
  * implied warranty.
  *
  * Ported to X11 and xscreensaver by jwz, 27-Feb-2002.
  * http://astronomy.swin.edu.au/~pbourke/modelling/fluid/
  */
 
+/* cjb notes
+ *
+ * Future ideas:
+ * Specifying a distribution in the ball sizes (with a gamma curve, possibly).
+ * Brownian motion, for that extra touch of realism.
+ */
+
 #include <math.h>
 #include "screenhack.h"
 #include <stdio.h>
 
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+#include "xdbe.h"
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
 
 typedef struct {
   Display *dpy;
   Window window;
   XWindowAttributes xgwa;
 
+  Pixmap b, ba;        /* double-buffer to reduce flicker */
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+  XdbeBackBuffer backb;
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
   GC draw_gc;          /* most of the balls */
   GC draw_gc2;         /* the ball being dragged with the mouse */
   GC erase_gc;
@@ -44,10 +59,14 @@ typedef struct {
   float *opx, *opy;    /* previous ball positions */
   float *r;            /* ball radiuses */
 
+  float *m;            /* ball mass, precalculated */
   float e;             /* coefficient of friction, I think? */
   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;
 
@@ -57,10 +76,22 @@ typedef struct {
   int font_baseline;
   int frame_count;
   int collision_count;
-
+  char fps_str[1024];
+  
 } b_state;
 
 
+/* Draws the frames per second string */
+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);
+  XDrawImageString (state->dpy, state->b, state->font_gc,
+                   0, state->xgwa.height - state->font_baseline,
+                   state->fps_str, strlen(state->fps_str));
+}
 
 /* Finds the origin of the window relative to the root window, by
    walking up the window tree until it reaches the top.
@@ -108,6 +139,7 @@ static void
 check_window_moved (b_state *state)
 {
   float oxmin = state->xmin;
+  float oxmax = state->xmax;
   float oymin = state->ymin;
   float oymax = state->ymax;
   int wx, wy;
@@ -118,14 +150,29 @@ check_window_moved (b_state *state)
   state->xmax = state->xmin + state->xgwa.width;
   state->ymax = state->ymin + state->xgwa.height - state->font_height;
 
-  /* Only need to erase the window if the origin moved */
-  if (oxmin != state->xmin || oymin != state->ymin)
-    XClearWindow (state->dpy, state->window);
-  else if (state->fps_p && oymax != state->ymax)
-    XFillRectangle (state->dpy, state->window, state->erase_gc,
-                    0, state->xgwa.height - state->font_height,
-                    state->xgwa.width, state->font_height);
-
+  if (state->dbuf && (state->ba))
+    {
+      if (oxmax != state->xmax || oymax != state->ymax)
+       {
+         XFreePixmap (state->dpy, state->ba);
+         state->ba = XCreatePixmap (state->dpy, state->window, 
+                                    state->xgwa.width, state->xgwa.height,
+                                    state->xgwa.depth);
+         XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
+                         state->xgwa.width, state->xgwa.height);
+         state->b = state->ba;
+       }
+    }
+  else 
+    {
+      /* Only need to erase the window if the origin moved */
+      if (oxmin != state->xmin || oymin != state->ymin)
+       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);
+    }
 }
 
 
@@ -177,7 +224,6 @@ recolor (b_state *state)
     XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel);
 }
 
-
 /* Initialize the state structure and various X data.
  */
 static b_state *
@@ -189,10 +235,37 @@ init_balls (Display *dpy, Window window)
   XGCValues gcv;
 
   state->dpy = dpy;
+
   state->window = window;
 
   check_window_moved (state);
 
+  state->dbuf = get_boolean_resource ("doubleBuffer", "Boolean");
+  state->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean");
+
+  if (state->dbuf)
+    {
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+      if (state->dbeclear_p)
+        state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
+      else
+        state->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
+      state->backb = state->b;
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+
+      if (!state->b)
+        {
+          state->ba = XCreatePixmap (state->dpy, state->window, 
+                                    state->xgwa.width, state->xgwa.height,
+                                    state->xgwa.depth);
+          state->b = state->ba;
+        }
+    }
+  else
+    {
+      state->b = state->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 (! (state->xgwa.all_event_masks & ButtonReleaseMask))
@@ -203,18 +276,23 @@ init_balls (Display *dpy, Window window)
                                       state->dpy, state->xgwa.colormap);
   gcv.background = get_pixel_resource("background", "Background",
                                       state->dpy, state->xgwa.colormap);
-  state->draw_gc = XCreateGC (state->dpy, state->window,
+  state->draw_gc = XCreateGC (state->dpy, state->b,
                               GCForeground|GCBackground, &gcv);
 
   gcv.foreground = get_pixel_resource("mouseForeground", "MouseForeground",
                                       state->dpy, state->xgwa.colormap);
-  state->draw_gc2 = XCreateGC (state->dpy, state->window,
+  state->draw_gc2 = XCreateGC (state->dpy, state->b,
                                GCForeground|GCBackground, &gcv);
 
   gcv.foreground = gcv.background;
-  state->erase_gc = XCreateGC (state->dpy, state->window,
+  state->erase_gc = XCreateGC (state->dpy, state->b,
                                GCForeground|GCBackground, &gcv);
 
+
+  if (state->ba) 
+    XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
+                   state->xgwa.width, state->xgwa.height);
+
   recolor (state);
 
   extx = state->xmax - state->xmin;
@@ -226,6 +304,8 @@ init_balls (Display *dpy, Window window)
   state->max_radius = get_float_resource ("size", "Size") / 2;
   if (state->max_radius < 1.0) state->max_radius = 1.0;
 
+  state->random_sizes_p = get_boolean_resource ("random", "Random");
+
   state->accx = get_float_resource ("wind", "Wind");
   if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0;
 
@@ -255,12 +335,13 @@ init_balls (Display *dpy, Window window)
       gcv.font = font->fid;
       gcv.foreground = get_pixel_resource("textColor", "Foreground",
                                           state->dpy, state->xgwa.colormap);
-      state->font_gc = XCreateGC(dpy, window,
+      state->font_gc = XCreateGC(dpy, state->b,
                                  GCFont|GCForeground|GCBackground, &gcv);
       state->font_height = font->ascent + font->descent;
       state->font_baseline = font->descent;
     }
 
+  state->m   = (float *) malloc (sizeof (*state->m)   * (state->count + 1));
   state->r   = (float *) malloc (sizeof (*state->r)   * (state->count + 1));
   state->vx  = (float *) malloc (sizeof (*state->vx)  * (state->count + 1));
   state->vy  = (float *) malloc (sizeof (*state->vy)  * (state->count + 1));
@@ -273,13 +354,16 @@ init_balls (Display *dpy, Window window)
     {
       state->px[i] = frand(extx) + state->xmin;
       state->py[i] = frand(exty) + state->ymin;
-      state->vx[i] = frand(0.2);
-      state->vy[i] = frand(0.2);
+      state->vx[i] = frand(0.2) - 0.1;
+      state->vy[i] = frand(0.2) - 0.1;
 
-      state->r[i] = state->max_radius;
+      state->r[i] = (state->random_sizes_p
+                     ? ((0.2 + frand(0.8)) * state->max_radius)
+                     : state->max_radius);
+      /*state->r[i] = pow(frand(1.0), state->sizegamma) * state->max_radius;*/
 
-      /* not quite: */
-      /* state->r[i] = 4 + (random() % (int) (state->max_radius - 4)); */
+      /* state->m[i] = pow(state->r[i],2) * M_PI; */
+      state->m[i] = pow(state->r[i],3) * M_PI * 1.3333;
     }
 
   return state;
@@ -331,11 +415,11 @@ 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. */
     {
-      static struct timeval last = { 0, };
       struct timeval now;
+      static struct timeval last = {0, };
 # ifdef GETTIMEOFDAY_TWO_ARGS
       struct timezone tzp;
       gettimeofday(&now, &tzp);
@@ -352,24 +436,19 @@ check_wall_clock (b_state *state, float max_d)
 
       state->time_since_shake += (now.tv_sec - last.tv_sec);
 
-      if (state->fps_p)
-        {
-          static char buf[1024];
-          float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
-                           (last.tv_sec + (last.tv_usec / 1000000.0)));
-          float fps = state->frame_count / elapsed;
-          float cps = state->collision_count / elapsed;
-
-          sprintf (buf, " FPS: %.2f  Collisions: %.f/frame  Max motion: %.3f",
-                   fps, cps/fps, max_d);
-
-          XFillRectangle (state->dpy, state->window, state->erase_gc,
-                          0, state->xgwa.height - state->font_height,
-                          state->xgwa.width, state->font_height);
-          XDrawImageString (state->dpy, state->window, state->font_gc,
-                            0, state->xgwa.height - state->font_baseline,
-                            buf, strlen(buf));
-        }
+      if (state->fps_p) 
+       {
+         float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
+                          (last.tv_sec + (last.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);
+         
+         draw_fps_string(state);
+       }
 
       state->frame_count = 0;
       state->collision_count = 0;
@@ -377,7 +456,6 @@ check_wall_clock (b_state *state, float max_d)
     }
 }
 
-
 /* Erases the balls at their previous positions, and draws the new ones.
  */
 static void
@@ -401,20 +479,26 @@ repaint_balls (b_state *state)
       x2b = (state->px[a] + state->r[a] - state->xmin);
       y2b = (state->py[a] + state->r[a] - state->ymin);
 
-/*      if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
-        {          
-          gc = state->erase_gc;
-          XFillArc (state->dpy, state->window, gc,
-                    x1a, y1a, x2a-x1a, y2a-y1a,
-                    0, 360*64);
-        }
-
+      if (!state->dbeclear_p ||
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+          !state->backb
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+         )
+       {
+/*       if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
+           {
+             gc = state->erase_gc;
+             XFillArc (state->dpy, state->b, gc,
+                       x1a, y1a, x2a-x1a, y2a-y1a,
+                       0, 360*64);
+           }
+       }
       if (state->mouse_ball == a)
         gc = state->draw_gc2;
       else
         gc = state->draw_gc;
 
-      XFillArc (state->dpy, state->window, gc,
+      XFillArc (state->dpy, state->b, gc,
                 x1b, y1b, x2b-x1b, y2b-y1b,
                 0, 360*64);
 
@@ -432,6 +516,25 @@ repaint_balls (b_state *state)
       state->opy[a] = state->py[a];
     }
 
+  if (state->fps_p && state->dbeclear_p) 
+    draw_fps_string(state);
+
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+  if (state->backb)
+    {
+      XdbeSwapInfo info[1];
+      info[0].swap_window = state->window;
+      info[0].swap_action = (state->dbeclear_p ? XdbeBackground : XdbeUndefined);
+      XdbeSwapBuffers (state->dpy, info, 1);
+    }
+  else
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
+  if (state->dbuf)
+    {
+      XCopyArea (state->dpy, state->b, state->window, state->erase_gc,
+                0, 0, state->xgwa.width, state->xgwa.height, 0, 0);
+    }
+
   if (state->shake_p && state->time_since_shake > 5)
     {
       max_d /= state->max_radius;
@@ -452,8 +555,9 @@ static void
 update_balls (b_state *state)
 {
   int a, b;
-  float d, nx, ny, m, vxa, vya, vxb, vyb, dd, cdx, cdy, cosam;
-  float dee2 = state->max_radius * state->max_radius * 4;
+  float d, vxa, vya, vxb, vyb, dd, cdx, cdy;
+  float ma, mb, vela, velb, vela1, velb1;
+  float dee2;
 
   check_window_moved (state);
 
@@ -485,54 +589,57 @@ update_balls (b_state *state)
                  (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);
-                vxa = state->vx[a];
-                vya = state->vy[a];
-                vxb = state->vx[b];
-                vyb = state->vy[b];
-                nx = state->px[b] - state->px[a];
-                ny = state->py[b] - state->py[a];
-                if (d < -0.0001 || d > 0.0001)
-                  {
-                    cdx = nx/d;
-                    cdy = ny/d;
-                    dd = state->r[a] + state->r[b] - d;
-                    state->px[a] -= dd*cdx;  /* just move them apart */
-                    state->py[a] -= dd*cdy;  /* no physical rationale, sorry */
-                    state->px[b] += dd*cdx;
-                    state->py[b] += dd*cdy;
-                    m = sqrt (state->vx[a] * state->vx[a] +
-                              state->vy[a] * state->vy[a]);
-                    if (m < -0.0001 || m > 0.0001)  /* A's velocity > 0 ? */
-                      {
-                        cosam = ((cdx * state->vx[a] + cdy * state->vy[a]) *
-                                 state->e);
-                        vxa -= cdx * cosam;
-                        vya -= cdy * cosam; /* conserve momentum */
-                        vxb += cdx * cosam;
-                        vyb += cdy * cosam;
-                      }
-                    m = sqrt (state->vx[b] *
-                              state->vx[b] +
-                              state->vy[b] *
-                              state->vy[b]);
-                    if (m < -0.0001 || m > 0.0001)
-                      {
-                        cosam = ((cdx * state->vx[b] + cdy * state->vy[b]) *
-                                 state->e);
-                        vxa += cdx * cosam;
-                        vya += cdy * cosam;
-                        vxb -= cdx * cosam;
-                        vyb -= cdy * cosam;
-                      }
+               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;
+#if 0
+                   vela1 += (frand (50) - 25) / ma; /* brownian motion */
+                   velb1 += (frand (50) - 25) / mb;
+#endif
+                   state->vx[a] = -cdx * vela1;
+                   state->vy[a] = -cdy * vela1;
+                   state->vx[b] = cdx * velb1;
+                   state->vy[b] = cdy * velb1;
                   }
-                state->vx[a] = vxa;
-                state->vy[a] = vya;
-                state->vx[b] = vxb;
-                state->vy[b] = vyb;
               }
           }
 
@@ -633,13 +740,19 @@ char *defaults [] = {
   "*delay:             10000",
   "*count:             300",
   "*size:              25",
+  "*random:            True",
   "*gravity:           0.01",
   "*wind:              0.00",
-  "*friction:          0.97",
+  "*friction:          0.8",
   "*timeScale:         1.0",
   "*doFPS:             False",
   "*shake:             True",
   "*shakeThreshold:    0.015",
+  "*doubleBuffer:      True",
+#ifdef HAVE_DOUBLE_BUFFER_EXTENSION
+  "*useDBE:            True",
+  "*useDBEClear:       True",
+#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
   0
 };
 
@@ -655,6 +768,10 @@ XrmOptionDescRec options [] = {
   { "-no-fps",         ".doFPS",       XrmoptionNoArg, "False" },
   { "-shake",          ".shake",       XrmoptionNoArg, "True" },
   { "-no-shake",       ".shake",       XrmoptionNoArg, "False" },
+  { "-random",         ".random",      XrmoptionNoArg, "True" },
+  { "-nonrandom",      ".random",      XrmoptionNoArg, "False" },
+  { "-db",             ".doubleBuffer", XrmoptionNoArg,  "True" },
+  { "-no-db",          ".doubleBuffer", XrmoptionNoArg,  "False" },
   { 0, 0, 0, 0 }
 };