X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Ffluidballs.c;h=0ce65db3f590bdfd0d44adebed3614ef435e6792;hb=96a411663168b0ba5432b407a83be55f3df0c802;hp=528c9994d4341617e261f9fe323120af8ccf3943;hpb=cccbddbc4140cf9a06d7d95cc5c0ca36eb5d6e28;p=xscreensaver diff --git a/hacks/fluidballs.c b/hacks/fluidballs.c index 528c9994..0ce65db3 100644 --- a/hacks/fluidballs.c +++ b/hacks/fluidballs.c @@ -5,24 +5,43 @@ * 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 + */ + +/* 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 #include "screenhack.h" #include +#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 +63,14 @@ typedef struct { 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; @@ -57,10 +80,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 +143,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 +154,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 +228,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 +239,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 +280,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,13 +308,15 @@ 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; 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"); @@ -255,12 +339,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,15 +358,21 @@ 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; } + memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1)); + memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1)); + return state; } @@ -331,11 +422,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, 0 }; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); @@ -352,24 +443,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 +463,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 +486,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 +523,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 +562,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, vca, vcb, dva, dvb; + float dee2; check_window_moved (state); @@ -476,68 +587,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])); - 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])) @@ -633,13 +745,19 @@ char *defaults [] = { "*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 }; @@ -650,11 +768,15 @@ XrmOptionDescRec options [] = { { "-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 } };