From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- hacks/fluidballs.c | 852 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 852 insertions(+) create mode 100644 hacks/fluidballs.c (limited to 'hacks/fluidballs.c') diff --git a/hacks/fluidballs.c b/hacks/fluidballs.c new file mode 100644 index 0000000..9273082 --- /dev/null +++ b/hacks/fluidballs.c @@ -0,0 +1,852 @@ +/* fluidballs, Copyright (c) 2000 by Peter Birtles + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * 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 + * 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; + 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 */ + GC draw_gc2; /* the ball being dragged with the mouse */ + GC erase_gc; + XColor fg; + XColor fg2; + + int count; /* number of balls */ + float xmin, ymin; /* rectangle of window, relative to root */ + float xmax, ymax; + + int mouse_ball; /* index of ball being dragged, or 0 if none. */ + + float tc; /* time constant (time-warp multiplier) */ + float accx; /* horizontal acceleration (wind) */ + float accy; /* vertical acceleration (gravity) */ + + float *vx, *vy; /* current ball velocities */ + float *px, *py; /* current ball positions */ + float *opx, *opy; /* previous ball positions */ + float *r; /* ball radiuses */ + + 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. */ + float shake_threshold; + int time_since_shake; + + Bool fps_p; /* Whether to draw some text at the bottom. */ + GC font_gc; + int font_height; + int font_baseline; + int frame_count; + int collision_count; + char fps_str[1024]; + + int time_tick; + struct timeval last_time; + +} 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*3 - 20, + state->xgwa.width, state->font_height*3 + 20); + XDrawImageString (state->dpy, state->b, state->font_gc, + 10, state->xgwa.height - state->font_height*2 - + state->font_baseline - 10, + 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. + */ +static void +window_origin (Display *dpy, Window window, int *x, int *y) +{ + XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen (dpy)), + 0, 0, x, y, &window); +} + + +/* Queries the window position to see if the window has moved or resized. + We poll this instead of waiting for ConfigureNotify events, because + when the window manager moves the window, only one ConfigureNotify + comes in: at the end of the motion. If we poll, we can react to the + new position while the window is still being moved. (Assuming the WM + does OpaqueMove, of course.) + */ +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; + XGetWindowAttributes (state->dpy, state->window, &state->xgwa); + window_origin (state->dpy, state->window, &wx, &wy); + state->xmin = wx; + state->ymin = wy; + state->xmax = state->xmin + state->xgwa.width; + state->ymax = state->ymin + state->xgwa.height - (state->font_height*3) - + (state->font_height ? 22 : 0); + + 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*3, + state->xgwa.width, state->font_height*3); + } +} + + +/* Returns the position of the mouse relative to the root window. + */ +static void +query_mouse (b_state *state, int *x, int *y) +{ + Window root1, child1; + int mouse_x, mouse_y, root_x, root_y; + unsigned int mask; + if (XQueryPointer (state->dpy, state->window, &root1, &child1, + &root_x, &root_y, &mouse_x, &mouse_y, &mask)) + { + *x = root_x; + *y = root_y; + } + else + { + *x = -9999; + *y = -9999; + } +} + +/* Re-pick the colors of the balls, and the mouse-ball. + */ +static void +recolor (b_state *state) +{ + if (state->fg.flags) + XFreeColors (state->dpy, state->xgwa.colormap, &state->fg.pixel, 1, 0); + if (state->fg2.flags) + XFreeColors (state->dpy, state->xgwa.colormap, &state->fg2.pixel, 1, 0); + + state->fg.flags = DoRed|DoGreen|DoBlue; + state->fg.red = 0x8888 + (random() % 0x8888); + state->fg.green = 0x8888 + (random() % 0x8888); + state->fg.blue = 0x8888 + (random() % 0x8888); + + state->fg2.flags = DoRed|DoGreen|DoBlue; + state->fg2.red = 0x8888 + (random() % 0x8888); + state->fg2.green = 0x8888 + (random() % 0x8888); + state->fg2.blue = 0x8888 + (random() % 0x8888); + + if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg)) + XSetForeground (state->dpy, state->draw_gc, state->fg.pixel); + + if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg2)) + XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel); +} + +/* Initialize the state structure and various X data. + */ +static void * +fluidballs_init (Display *dpy, Window window) +{ + int i; + float extx, exty; + b_state *state = (b_state *) calloc (1, sizeof(*state)); + 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 (dpy, "doubleBuffer", "Boolean"); + +# ifdef HAVE_JWXYZ /* 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 + 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 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(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(state->dpy, state->xgwa.colormap, + "mouseForeground", "MouseForeground"); + state->draw_gc2 = XCreateGC (state->dpy, state->b, + GCForeground|GCBackground, &gcv); + + gcv.foreground = gcv.background; + 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; + exty = state->ymax - state->ymin; + + state->count = get_integer_resource (dpy, "count", "Count"); + if (state->count < 1) state->count = 20; + + state->max_radius = get_float_resource (dpy, "size", "Size") / 2; + if (state->max_radius < 1.0) state->max_radius = 1.0; + + if (state->xgwa.width > 2560) state->max_radius *= 2; /* Retina displays */ + + if (state->xgwa.width < 100 || state->xgwa.height < 100) /* tiny window */ + { + if (state->max_radius > 5) + state->max_radius = 5; + } + + state->random_sizes_p = get_boolean_resource (dpy, "random", "Random"); + + /* 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 (dpy, "gravity", "Gravity"); + if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01; + + 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 (dpy, "timeScale", "TimeScale"); + if (state->tc <= 0 || state->tc > 10) state->tc = 1.0; + + state->shake_p = get_boolean_resource (dpy, "shake", "Shake"); + state->shake_threshold = get_float_resource (dpy, "shakeThreshold", + "ShakeThreshold"); + state->time_tick = 999999; + +# ifdef HAVE_MOBILE /* 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 (dpy, "fpsFont", "Font"); + if (!fontname) fontname = "-*-courier-bold-r-normal-*-180-*"; + font = load_font_retry (dpy, fontname); + if (!font) abort(); + gcv.font = font->fid; + 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; + 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 = (float *) malloc (sizeof (*state->px) * (state->count + 1)); + state->py = (float *) malloc (sizeof (*state->py) * (state->count + 1)); + state->opx = (float *) malloc (sizeof (*state->opx) * (state->count + 1)); + state->opy = (float *) malloc (sizeof (*state->opy) * (state->count + 1)); + + for (i=1; i<=state->count; i++) + { + state->px[i] = frand(extx) + state->xmin; + state->py[i] = frand(exty) + state->ymin; + state->vx[i] = frand(0.2) - 0.1; + state->vy[i] = frand(0.2) - 0.1; + + 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;*/ + + /* 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; +} + + +/* Messes with gravity: permute "down" to be in a random direction. + */ +static void +shake (b_state *state) +{ + float a = state->accx; + float b = state->accy; + int i = random() % 4; + + switch (i) + { + case 0: + state->accx = a; + state->accy = b; + break; + case 1: + state->accx = -a; + state->accy = -b; + break; + case 2: + state->accx = b; + state->accy = a; + break; + case 3: + state->accx = -b; + state->accy = -a; + break; + default: + abort(); + break; + } + + state->time_since_shake = 0; + recolor (state); +} + + +/* Look at the current time, and update state->time_since_shake. + Draw the FPS display if desired. + */ +static void +check_wall_clock (b_state *state, float max_d) +{ + state->frame_count++; + + if (state->time_tick++ > 20) /* don't call gettimeofday() too often -- it's slow. */ + { + struct timeval now; +# ifdef GETTIMEOFDAY_TWO_ARGS + struct timezone tzp; + gettimeofday(&now, &tzp); +# else + gettimeofday(&now); +# endif + + if (state->last_time.tv_sec == 0) + state->last_time = now; + + state->time_tick = 0; + if (now.tv_sec == state->last_time.tv_sec) + return; + + state->time_since_shake += (now.tv_sec - state->last_time.tv_sec); + +# ifdef HAVE_MOBILE /* Always obey real-world gravity */ + { + float a = fabs (fabs(state->accx) > fabs(state->accy) + ? state->accx : state->accy); + int rot = current_device_rotation(); + switch (rot) { + 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 /* HAVE_MOBILE */ + + if (state->fps_p) + { + float elapsed = ((now.tv_sec + (now.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, "Collisions: %.3f/frame Max motion: %.3f", + cps/fps, max_d); + + draw_fps_string(state); + } + + state->frame_count = 0; + state->collision_count = 0; + state->last_time = now; + } +} + +/* Erases the balls at their previous positions, and draws the new ones. + */ +static void +repaint_balls (b_state *state) +{ + int a; +# ifndef HAVE_JWXYZ + int x1a, x2a, y1a, y2a; +# endif + int x1b, x2b, y1b, y2b; + float max_d = 0; + +#ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ + XClearWindow (state->dpy, state->b); +#endif + + for (a=1; a <= state->count; a++) + { + GC gc; +# ifndef HAVE_JWXYZ + x1a = (state->opx[a] - state->r[a] - state->xmin); + y1a = (state->opy[a] - state->r[a] - state->ymin); + x2a = (state->opx[a] + state->r[a] - state->xmin); + y2a = (state->opy[a] + state->r[a] - state->ymin); +# endif + + x1b = (state->px[a] - state->r[a] - state->xmin); + y1b = (state->py[a] - state->r[a] - state->ymin); + x2b = (state->px[a] + state->r[a] - state->xmin); + y2b = (state->py[a] + state->r[a] - state->ymin); + +#ifndef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ +#ifdef HAVE_DOUBLE_BUFFER_EXTENSION + if (!state->dbeclear_p || !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); + } + } +#endif /* !HAVE_JWXYZ */ + + if (state->mouse_ball == a) + gc = state->draw_gc2; + else + gc = state->draw_gc; + + XFillArc (state->dpy, state->b, gc, + x1b, y1b, x2b-x1b, y2b-y1b, + 0, 360*64); + + if (state->shake_p) + { + /* distance this ball moved this frame */ + float d = ((state->px[a] - state->opx[a]) * + (state->px[a] - state->opx[a]) + + (state->py[a] - state->opy[a]) * + (state->py[a] - state->opy[a])); + if (d > max_d) max_d = d; + } + + state->opx[a] = state->px[a]; + state->opy[a] = state->py[a]; + } + + 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 + 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; + if (max_d < state->shake_threshold || /* when its stable */ + state->time_since_shake > 30) /* or when 30 secs has passed */ + { + shake (state); + } + } + + check_wall_clock (state, max_d); +} + + +/* Implements the laws of physics: move balls to their new positions. + */ +static void +update_balls (b_state *state) +{ + int a, b; + float d, vxa, vya, vxb, vyb, dd, cdx, cdy; + float ma, mb, vca, vcb, dva, dvb; + float dee2; + + check_window_moved (state); + + /* If we're currently tracking the mouse, update that ball first. + */ + if (state->mouse_ball != 0) + { + int mouse_x, mouse_y; + query_mouse (state, &mouse_x, &mouse_y); + state->px[state->mouse_ball] = mouse_x; + state->py[state->mouse_ball] = mouse_y; + state->vx[state->mouse_ball] = + (0.1 * + (state->px[state->mouse_ball] - state->opx[state->mouse_ball]) * + state->tc); + state->vy[state->mouse_ball] = + (0.1 * + (state->py[state->mouse_ball] - state->opy[state->mouse_ball]) * + state->tc); + } + + /* For each ball, compute the influence of every other ball. */ + 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. + */ + for (a=1; a <= state->count; a++) + { + if (state->px[a] <= (state->xmin + state->r[a])) + { + state->px[a] = state->xmin + state->r[a]; + state->vx[a] = -state->vx[a] * state->e; + } + if (state->px[a] >= (state->xmax - state->r[a])) + { + state->px[a] = state->xmax - state->r[a]; + state->vx[a] = -state->vx[a] * state->e; + } + if (state->py[a] <= (state->ymin + state->r[a])) + { + state->py[a] = state->ymin + state->r[a]; + state->vy[a] = -state->vy[a] * state->e; + } + if (state->py[a] >= (state->ymax - state->r[a])) + { + state->py[a] = state->ymax - state->r[a]; + state->vy[a] = -state->vy[a] * state->e; + } + } + + /* Apply gravity to all balls. + */ + for (a=1; a <= state->count; a++) + if (a != state->mouse_ball) + { + state->vx[a] += state->accx * state->tc; + state->vy[a] += state->accy * state->tc; + state->px[a] += state->vx[a] * state->tc; + state->py[a] += state->vy[a] * state->tc; + } +} + + +/* Handle X events, specifically, allow a ball to be picked up with the mouse. + */ +static Bool +fluidballs_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + b_state *state = (b_state *) closure; + + 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 + { + /* 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] - 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 True; + } + } + } + } + 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; +} + +static void +fluidballs_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ +} + +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", + ".foreground: yellow", + ".textColor: yellow", + "*mouseForeground: white", + "*delay: 10000", + "*count: 300", + "*size: 25", + "*random: True", + "*gravity: 0.01", + "*wind: 0.00", + "*elasticity: 0.97", + "*timeScale: 1.0", + "*shake: True", + "*shakeThreshold: 0.015", + "*doubleBuffer: True", +#ifdef HAVE_DOUBLE_BUFFER_EXTENSION + "*useDBE: True", + "*useDBEClear: True", +#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ +#ifdef HAVE_MOBILE + "*ignoreRotation: True", +#endif + 0 +}; + +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 }, + { "-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 } +}; + + +XSCREENSAVER_MODULE ("FluidBalls", fluidballs) -- cgit v1.2.3-55-g7522