* 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/
+ *
+ * Some physics improvements by Steven Barker <steve@blckknght.org>
+ */
+
+/* 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>
#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;
float *opx, *opy; /* previous ball positions */
float *r; /* ball radiuses */
- float e; /* coefficient of friction, I think? */
+ float *m; /* ball mass, precalculated */
+ 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;
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.
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;
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);
+ }
}
XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel);
}
-
/* Initialize the state structure and various X data.
*/
static b_state *
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))
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;
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;
state->accy = get_float_resource ("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 ("elasticity", "Elacitcity");
if (state->e < 0.2 || state->e > 1.0) state->e = 0.97;
state->tc = get_float_resource ("timeScale", "TimeScale");
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));
{
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;
{
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);
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;
}
}
-
/* Erases the balls at their previous positions, and draws the new ones.
*/
static void
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);
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;
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, vca, vcb, dva, dvb;
+ float dee2;
check_window_moved (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]));
- 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;
- }
- }
- state->vx[a] = vxa;
- state->vy[a] = vya;
- state->vx[b] = vxb;
- state->vy[b] = vyb;
- }
- }
+ 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
+ dva += (frand (50) - 25) / ma; /* q: why are elves so chaotic? */
+ dvb += (frand (50) - 25) / mb; /* a: brownian motion. */
+#endif
+
+ 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.
- */
+ /* Force all balls to be on screen.
+ */
for (a=1; a <= state->count; a++)
{
if (state->px[a] <= (state->xmin + state->r[a]))
"*delay: 10000",
"*count: 300",
"*size: 25",
+ "*random: True",
"*gravity: 0.01",
"*wind: 0.00",
- "*friction: 0.97",
+ "*elasticity: 0.97",
"*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
};
{ "-count", ".count", XrmoptionSepArg, 0 },
{ "-gravity", ".gravity", XrmoptionSepArg, 0 },
{ "-wind", ".wind", XrmoptionSepArg, 0 },
- { "-friction", ".friction", XrmoptionSepArg, 0 },
+ { "-elasticity", ".elasticity", XrmoptionSepArg, 0 },
{ "-fps", ".doFPS", XrmoptionNoArg, "True" },
{ "-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 }
};