958f7bc1b37272f34699143ef2380e9c572e1044
[xscreensaver] / hacks / fluidballs.c
1 /* fluidballs, Copyright (c) 2000 by Peter Birtles <peter@bqdesign.com.au>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Ported to X11 and xscreensaver by jwz, 27-Feb-2002.
12  *
13  * http://astronomy.swin.edu.au/~pbourke/modelling/fluid/
14  *
15  * Some physics improvements by Steven Barker <steve@blckknght.org>
16  */
17
18 /* Future ideas:
19  * Specifying a distribution in the ball sizes (with a gamma curve, possibly).
20  * Brownian motion, for that extra touch of realism.
21  *
22  * It would be nice to detect when there are more balls than fit in
23  * the window, and scale the number of balls back.  Useful for the
24  * xscreensaver-demo preview, which is often too tight by default.
25  */
26
27 #include <math.h>
28 #include "screenhack.h"
29 #include <stdio.h>
30
31 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
32 #include "xdbe.h"
33 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
34
35 typedef struct {
36   Display *dpy;
37   Window window;
38   XWindowAttributes xgwa;
39   int delay;
40
41   Pixmap b, ba; /* double-buffer to reduce flicker */
42 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
43   XdbeBackBuffer backb;
44   Bool dbeclear_p;
45 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
46
47   GC draw_gc;           /* most of the balls */
48   GC draw_gc2;          /* the ball being dragged with the mouse */
49   GC erase_gc;
50   XColor fg;
51   XColor fg2;
52
53   int count;            /* number of balls */
54   float xmin, ymin;     /* rectangle of window, relative to root */
55   float xmax, ymax;
56
57   int mouse_ball;       /* index of ball being dragged, or 0 if none. */
58
59   float tc;             /* time constant (time-warp multiplier) */
60   float accx;           /* horizontal acceleration (wind) */
61   float accy;           /* vertical acceleration (gravity) */
62
63   float *vx,  *vy;      /* current ball velocities */
64   float *px,  *py;      /* current ball positions */
65   float *opx, *opy;     /* previous ball positions */
66   float *r;             /* ball radiuses */
67
68   float *m;             /* ball mass, precalculated */
69   float e;              /* coeficient of elasticity */
70   float max_radius;     /* largest radius of any ball */
71
72   Bool random_sizes_p;  /* Whether balls should be various sizes up to max. */
73   Bool shake_p;         /* Whether to mess with gravity when things settle. */
74   Bool dbuf;            /* Whether we're using double buffering. */
75   float shake_threshold;
76   int time_since_shake;
77
78   Bool fps_p;           /* Whether to draw some text at the bottom. */
79   GC font_gc;
80   int font_height;
81   int font_baseline;
82   int frame_count;
83   int collision_count;
84   char fps_str[1024];
85   
86   int time_tick;
87   struct timeval last_time;
88
89 } b_state;
90
91
92 /* Draws the frames per second string */
93 static void 
94 draw_fps_string (b_state *state)
95 {  
96   XFillRectangle (state->dpy, state->b, state->erase_gc,
97                   0, state->xgwa.height - state->font_height*3 - 20,
98                   state->xgwa.width, state->font_height*3 + 20);
99   XDrawImageString (state->dpy, state->b, state->font_gc,
100                     10, state->xgwa.height - state->font_height*2 -
101                     state->font_baseline - 10,
102                     state->fps_str, strlen(state->fps_str));
103 }
104
105 /* Finds the origin of the window relative to the root window, by
106    walking up the window tree until it reaches the top.
107  */
108 static void
109 window_origin (Display *dpy, Window window, int *x, int *y)
110 {
111   XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen (dpy)),
112                          0, 0, x, y, &window);
113 }
114
115
116 /* Queries the window position to see if the window has moved or resized.
117    We poll this instead of waiting for ConfigureNotify events, because
118    when the window manager moves the window, only one ConfigureNotify
119    comes in: at the end of the motion.  If we poll, we can react to the
120    new position while the window is still being moved.  (Assuming the WM
121    does OpaqueMove, of course.)
122  */
123 static void
124 check_window_moved (b_state *state)
125 {
126   float oxmin = state->xmin;
127   float oxmax = state->xmax;
128   float oymin = state->ymin;
129   float oymax = state->ymax;
130   int wx, wy;
131   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
132   window_origin (state->dpy, state->window, &wx, &wy);
133   state->xmin = wx;
134   state->ymin = wy;
135   state->xmax = state->xmin + state->xgwa.width;
136   state->ymax = state->ymin + state->xgwa.height - (state->font_height*3) -
137     (state->font_height ? 22 : 0);
138
139   if (state->dbuf && (state->ba))
140     {
141       if (oxmax != state->xmax || oymax != state->ymax)
142         {
143           XFreePixmap (state->dpy, state->ba);
144           state->ba = XCreatePixmap (state->dpy, state->window, 
145                                      state->xgwa.width, state->xgwa.height,
146                                      state->xgwa.depth);
147           XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
148                           state->xgwa.width, state->xgwa.height);
149           state->b = state->ba;
150         }
151     }
152   else 
153     {
154       /* Only need to erase the window if the origin moved */
155       if (oxmin != state->xmin || oymin != state->ymin)
156         XClearWindow (state->dpy, state->window);
157       else if (state->fps_p && oymax != state->ymax)
158         XFillRectangle (state->dpy, state->b, state->erase_gc,
159                         0, state->xgwa.height - state->font_height*3,
160                         state->xgwa.width, state->font_height*3);
161     }
162 }
163
164
165 /* Returns the position of the mouse relative to the root window.
166  */
167 static void
168 query_mouse (b_state *state, int *x, int *y)
169 {
170   Window root1, child1;
171   int mouse_x, mouse_y, root_x, root_y;
172   unsigned int mask;
173   if (XQueryPointer (state->dpy, state->window, &root1, &child1,
174                      &root_x, &root_y, &mouse_x, &mouse_y, &mask))
175     {
176       *x = root_x;
177       *y = root_y;
178     }
179   else
180     {
181       *x = -9999;
182       *y = -9999;
183     }
184 }
185
186 /* Re-pick the colors of the balls, and the mouse-ball.
187  */
188 static void
189 recolor (b_state *state)
190 {
191   if (state->fg.flags)
192     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg.pixel, 1, 0);
193   if (state->fg2.flags)
194     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg2.pixel, 1, 0);
195
196   state->fg.flags  = DoRed|DoGreen|DoBlue;
197   state->fg.red    = 0x8888 + (random() % 0x8888);
198   state->fg.green  = 0x8888 + (random() % 0x8888);
199   state->fg.blue   = 0x8888 + (random() % 0x8888);
200
201   state->fg2.flags = DoRed|DoGreen|DoBlue;
202   state->fg2.red   = 0x8888 + (random() % 0x8888);
203   state->fg2.green = 0x8888 + (random() % 0x8888);
204   state->fg2.blue  = 0x8888 + (random() % 0x8888);
205
206   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg))
207     XSetForeground (state->dpy, state->draw_gc,  state->fg.pixel);
208
209   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg2))
210     XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel);
211 }
212
213 /* Initialize the state structure and various X data.
214  */
215 static void *
216 fluidballs_init (Display *dpy, Window window)
217 {
218   int i;
219   float extx, exty;
220   b_state *state = (b_state *) calloc (1, sizeof(*state));
221   XGCValues gcv;
222
223   state->dpy = dpy;
224   state->window = window;
225   state->delay = get_integer_resource (dpy, "delay", "Integer");
226
227   check_window_moved (state);
228
229   state->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
230
231 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
232   state->dbuf = False;
233 # endif
234
235   if (state->dbuf)
236     {
237 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
238       state->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
239       if (state->dbeclear_p)
240         state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
241       else
242         state->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
243       state->backb = state->b;
244 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
245
246       if (!state->b)
247         {
248           state->ba = XCreatePixmap (state->dpy, state->window, 
249                                      state->xgwa.width, state->xgwa.height,
250                                      state->xgwa.depth);
251           state->b = state->ba;
252         }
253     }
254   else
255     {
256       state->b = state->window;
257     }
258
259   /* Select ButtonRelease events on the external window, if no other app has
260      already selected it (only one app can select it at a time: BadAccess. */
261 #if 0
262   if (! (state->xgwa.all_event_masks & ButtonReleaseMask))
263     XSelectInput (state->dpy, state->window,
264                   state->xgwa.your_event_mask | ButtonReleaseMask);
265 #endif
266
267   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
268                                       "foreground", "Foreground");
269   gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
270                                       "background", "Background");
271   state->draw_gc = XCreateGC (state->dpy, state->b,
272                               GCForeground|GCBackground, &gcv);
273
274   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
275                                       "mouseForeground", "MouseForeground");
276   state->draw_gc2 = XCreateGC (state->dpy, state->b,
277                                GCForeground|GCBackground, &gcv);
278
279   gcv.foreground = gcv.background;
280   state->erase_gc = XCreateGC (state->dpy, state->b,
281                                GCForeground|GCBackground, &gcv);
282
283
284   if (state->ba) 
285     XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
286                     state->xgwa.width, state->xgwa.height);
287
288   recolor (state);
289
290   extx = state->xmax - state->xmin;
291   exty = state->ymax - state->ymin;
292
293   state->count = get_integer_resource (dpy, "count", "Count");
294   if (state->count < 1) state->count = 20;
295
296   state->max_radius = get_float_resource (dpy, "size", "Size") / 2;
297   if (state->max_radius < 1.0) state->max_radius = 1.0;
298
299   state->random_sizes_p = get_boolean_resource (dpy, "random", "Random");
300
301   /* If the initial window size is too small to hold all these balls,
302      make fewer of them...
303    */
304   {
305     float r = (state->random_sizes_p
306                ? state->max_radius * 0.7
307                : state->max_radius);
308     float ball_area = M_PI * r * r;
309     float balls_area = state->count * ball_area;
310     float window_area = state->xgwa.width * state->xgwa.height;
311     window_area *= 0.75;  /* don't pack it completely full */
312     if (balls_area > window_area)
313       state->count = window_area / ball_area;
314   }
315
316   state->accx = get_float_resource (dpy, "wind", "Wind");
317   if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0;
318
319   state->accy = get_float_resource (dpy, "gravity", "Gravity");
320   if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01;
321
322   state->e = get_float_resource (dpy, "elasticity", "Elacitcity");
323   if (state->e < 0.2 || state->e > 1.0) state->e = 0.97;
324
325   state->tc = get_float_resource (dpy, "timeScale", "TimeScale");
326   if (state->tc <= 0 || state->tc > 10) state->tc = 1.0;
327
328   state->shake_p = get_boolean_resource (dpy, "shake", "Shake");
329   state->shake_threshold = get_float_resource (dpy, "shakeThreshold",
330                                                "ShakeThreshold");
331
332   state->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
333   if (state->fps_p)
334     {
335       XFontStruct *font;
336       char *fontname = get_string_resource (dpy, "fpsFont", "Font");
337       if (!fontname) fontname = "-*-courier-bold-r-normal-*-180-*";
338       font = XLoadQueryFont (dpy, fontname);
339       if (!font) font = XLoadQueryFont (dpy, "fixed");
340       if (!font) exit(-1);
341       gcv.font = font->fid;
342       gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
343                                           "textColor", "Foreground");
344       state->font_gc = XCreateGC(dpy, state->b,
345                                  GCFont|GCForeground|GCBackground, &gcv);
346       state->font_height = font->ascent + font->descent;
347       state->font_baseline = font->descent;
348     }
349
350   state->m   = (float *) malloc (sizeof (*state->m)   * (state->count + 1));
351   state->r   = (float *) malloc (sizeof (*state->r)   * (state->count + 1));
352   state->vx  = (float *) malloc (sizeof (*state->vx)  * (state->count + 1));
353   state->vy  = (float *) malloc (sizeof (*state->vy)  * (state->count + 1));
354   state->px  = (float *) malloc (sizeof (*state->px)  * (state->count + 1));
355   state->py  = (float *) malloc (sizeof (*state->py)  * (state->count + 1));
356   state->opx = (float *) malloc (sizeof (*state->opx) * (state->count + 1));
357   state->opy = (float *) malloc (sizeof (*state->opy) * (state->count + 1));
358
359   for (i=1; i<=state->count; i++)
360     {
361       state->px[i] = frand(extx) + state->xmin;
362       state->py[i] = frand(exty) + state->ymin;
363       state->vx[i] = frand(0.2) - 0.1;
364       state->vy[i] = frand(0.2) - 0.1;
365
366       state->r[i] = (state->random_sizes_p
367                      ? ((0.2 + frand(0.8)) * state->max_radius)
368                      : state->max_radius);
369       /*state->r[i] = pow(frand(1.0), state->sizegamma) * state->max_radius;*/
370
371       /* state->m[i] = pow(state->r[i],2) * M_PI; */
372       state->m[i] = pow(state->r[i],3) * M_PI * 1.3333;
373     }
374
375   memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1));
376   memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1));
377
378   return state;
379 }
380
381
382 /* Messes with gravity: permute "down" to be in a random direction.
383  */
384 static void
385 shake (b_state *state)
386 {
387   float a = state->accx;
388   float b = state->accy;
389   int i = random() % 4;
390
391   switch (i)
392     {
393     case 0:
394       state->accx = a;
395       state->accy = b;
396       break;
397     case 1:
398       state->accx = -a;
399       state->accy = -b;
400       break;
401     case 2:
402       state->accx = b;
403       state->accy = a;
404       break;
405     case 3:
406       state->accx = -b;
407       state->accy = -a;
408       break;
409     default:
410       abort();
411       break;
412     }
413
414   state->time_since_shake = 0;
415   recolor (state);
416 }
417
418
419 /* Look at the current time, and update state->time_since_shake.
420    Draw the FPS display if desired.
421  */
422 static void
423 check_wall_clock (b_state *state, float max_d)
424 {
425   state->frame_count++;
426   
427   if (state->time_tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
428     {
429       struct timeval now;
430 # ifdef GETTIMEOFDAY_TWO_ARGS
431       struct timezone tzp;
432       gettimeofday(&now, &tzp);
433 # else
434       gettimeofday(&now);
435 # endif
436
437       if (state->last_time.tv_sec == 0)
438         state->last_time = now;
439
440       state->time_tick = 0;
441       if (now.tv_sec == state->last_time.tv_sec)
442         return;
443
444       state->time_since_shake += (now.tv_sec - state->last_time.tv_sec);
445
446       if (state->fps_p) 
447         {
448           float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
449                            (state->last_time.tv_sec + (state->last_time.tv_usec / 1000000.0)));
450           float fps = state->frame_count / elapsed;
451           float cps = state->collision_count / elapsed;
452           
453           sprintf (state->fps_str, "Collisions: %.3f/frame  Max motion: %.3f",
454                    cps/fps, max_d);
455           
456           draw_fps_string(state);
457         }
458
459       state->frame_count = 0;
460       state->collision_count = 0;
461       state->last_time = now;
462     }
463 }
464
465 /* Erases the balls at their previous positions, and draws the new ones.
466  */
467 static void
468 repaint_balls (b_state *state)
469 {
470   int a;
471   int x1a, x2a, y1a, y2a;
472   int x1b, x2b, y1b, y2b;
473   float max_d = 0;
474
475 #ifdef HAVE_COCOA       /* Don't second-guess Quartz's double-buffering */
476   XClearWindow (state->dpy, state->b);
477 #endif
478
479   for (a=1; a <= state->count; a++)
480     {
481       GC gc;
482       x1a = (state->opx[a] - state->r[a] - state->xmin);
483       y1a = (state->opy[a] - state->r[a] - state->ymin);
484       x2a = (state->opx[a] + state->r[a] - state->xmin);
485       y2a = (state->opy[a] + state->r[a] - state->ymin);
486
487       x1b = (state->px[a] - state->r[a] - state->xmin);
488       y1b = (state->py[a] - state->r[a] - state->ymin);
489       x2b = (state->px[a] + state->r[a] - state->xmin);
490       y2b = (state->py[a] + state->r[a] - state->ymin);
491
492 #ifndef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
493 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
494       if (!state->dbeclear_p || !state->backb)
495 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
496         {
497 /*        if (x1a != x1b || y1a != y1b)   -- leaves turds if we optimize this */
498             {
499               gc = state->erase_gc;
500               XFillArc (state->dpy, state->b, gc,
501                         x1a, y1a, x2a-x1a, y2a-y1a,
502                         0, 360*64);
503             }
504         }
505 #endif /* !HAVE_COCOA */
506
507       if (state->mouse_ball == a)
508         gc = state->draw_gc2;
509       else
510         gc = state->draw_gc;
511
512       XFillArc (state->dpy, state->b, gc,
513                 x1b, y1b, x2b-x1b, y2b-y1b,
514                 0, 360*64);
515
516       if (state->shake_p)
517         {
518           /* distance this ball moved this frame */
519           float d = ((state->px[a] - state->opx[a]) *
520                      (state->px[a] - state->opx[a]) +
521                      (state->py[a] - state->opy[a]) *
522                      (state->py[a] - state->opy[a]));
523           if (d > max_d) max_d = d;
524         }
525
526       state->opx[a] = state->px[a];
527       state->opy[a] = state->py[a];
528     }
529
530   if (state->fps_p
531 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
532       && (state->backb ? state->dbeclear_p : 1)
533 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
534       )
535     draw_fps_string(state);
536
537 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
538   if (state->backb)
539     {
540       XdbeSwapInfo info[1];
541       info[0].swap_window = state->window;
542       info[0].swap_action = (state->dbeclear_p ? XdbeBackground : XdbeUndefined);
543       XdbeSwapBuffers (state->dpy, info, 1);
544     }
545   else
546 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
547   if (state->dbuf)
548     {
549       XCopyArea (state->dpy, state->b, state->window, state->erase_gc,
550                  0, 0, state->xgwa.width, state->xgwa.height, 0, 0);
551     }
552
553   if (state->shake_p && state->time_since_shake > 5)
554     {
555       max_d /= state->max_radius;
556       if (max_d < state->shake_threshold ||  /* when its stable */
557           state->time_since_shake > 30)      /* or when 30 secs has passed */
558         {
559           shake (state);
560         }
561     }
562
563   check_wall_clock (state, max_d);
564 }
565
566
567 /* Implements the laws of physics: move balls to their new positions.
568  */
569 static void
570 update_balls (b_state *state)
571 {
572   int a, b;
573   float d, vxa, vya, vxb, vyb, dd, cdx, cdy;
574   float ma, mb, vca, vcb, dva, dvb;
575   float dee2;
576
577   check_window_moved (state);
578
579   /* If we're currently tracking the mouse, update that ball first.
580    */
581   if (state->mouse_ball != 0)
582     {
583       int mouse_x, mouse_y;
584       query_mouse (state, &mouse_x, &mouse_y);
585       state->px[state->mouse_ball] = mouse_x;
586       state->py[state->mouse_ball] = mouse_y;
587       state->vx[state->mouse_ball] =
588         (0.1 *
589          (state->px[state->mouse_ball] - state->opx[state->mouse_ball]) *
590          state->tc);
591       state->vy[state->mouse_ball] =
592         (0.1 *
593          (state->py[state->mouse_ball] - state->opy[state->mouse_ball]) *
594          state->tc);
595     }
596
597   /* For each ball, compute the influence of every other ball. */
598   for (a=1; a <= state->count -  1; a++)
599     for (b=a + 1; b <= state->count; b++)
600       {
601          d = ((state->px[a] - state->px[b]) *
602               (state->px[a] - state->px[b]) +
603               (state->py[a] - state->py[b]) *
604               (state->py[a] - state->py[b]));
605          dee2 = (state->r[a] + state->r[b]) *
606                 (state->r[a] + state->r[b]);
607          if (d < dee2)
608          {
609             state->collision_count++;
610             d = sqrt(d);
611             dd = state->r[a] + state->r[b] - d;
612
613             cdx = (state->px[b] - state->px[a]) / d;
614             cdy = (state->py[b] - state->py[a]) / d;
615
616             /* Move each ball apart from the other by half the
617              * 'collision' distance.
618              */
619             state->px[a] -= 0.5 * dd * cdx;
620             state->py[a] -= 0.5 * dd * cdy;
621             state->px[b] += 0.5 * dd * cdx;
622             state->py[b] += 0.5 * dd * cdy;
623
624             ma = state->m[a];
625             mb = state->m[b];
626
627             vxa = state->vx[a];
628             vya = state->vy[a];
629             vxb = state->vx[b];
630             vyb = state->vy[b];
631
632             vca = vxa * cdx + vya * cdy; /* the component of each velocity */
633             vcb = vxb * cdx + vyb * cdy; /* along the axis of the collision */
634
635             /* elastic collison */
636             dva = (vca * (ma - mb) + vcb * 2 * mb) / (ma + mb) - vca;
637             dvb = (vcb * (mb - ma) + vca * 2 * ma) / (ma + mb) - vcb;
638
639             dva *= state->e; /* some energy lost to inelasticity */
640             dvb *= state->e;
641
642 #if 0
643             dva += (frand (50) - 25) / ma;   /* q: why are elves so chaotic? */
644             dvb += (frand (50) - 25) / mb;   /* a: brownian motion. */
645 #endif
646
647             vxa += dva * cdx;
648             vya += dva * cdy;
649             vxb += dvb * cdx;
650             vyb += dvb * cdy;
651
652             state->vx[a] = vxa;
653             state->vy[a] = vya;
654             state->vx[b] = vxb;
655             state->vy[b] = vyb;
656          }
657       }
658
659    /* Force all balls to be on screen.
660     */
661   for (a=1; a <= state->count; a++)
662     {
663       if (state->px[a] <= (state->xmin + state->r[a]))
664         {
665           state->px[a] = state->xmin + state->r[a];
666           state->vx[a] = -state->vx[a] * state->e;
667         }
668       if (state->px[a] >= (state->xmax - state->r[a]))
669         {
670           state->px[a] = state->xmax - state->r[a];
671           state->vx[a] = -state->vx[a] * state->e;
672         }
673       if (state->py[a] <= (state->ymin + state->r[a]))
674         {
675           state->py[a] = state->ymin + state->r[a];
676           state->vy[a] = -state->vy[a] * state->e;
677         }
678       if (state->py[a] >= (state->ymax - state->r[a]))
679         {
680           state->py[a] = state->ymax - state->r[a];
681           state->vy[a] = -state->vy[a] * state->e;
682         }
683     }
684
685   /* Apply gravity to all balls.
686    */
687   for (a=1; a <= state->count; a++)
688     if (a != state->mouse_ball)
689       {
690         state->vx[a] += state->accx * state->tc;
691         state->vy[a] += state->accy * state->tc;
692         state->px[a] += state->vx[a] * state->tc;
693         state->py[a] += state->vy[a] * state->tc;
694       }
695 }
696
697
698 /* Handle X events, specifically, allow a ball to be picked up with the mouse.
699  */
700 static Bool
701 fluidballs_event (Display *dpy, Window window, void *closure, XEvent *event)
702 {
703   b_state *state = (b_state *) closure;
704
705   if (event->xany.type == ButtonPress)
706     {
707       int i, rx, ry;
708       XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen(dpy)),
709                              event->xbutton.x, event->xbutton.y, &rx, &ry,
710                              &window);
711
712       if (state->mouse_ball != 0)  /* second down-click?  drop the ball. */
713         {
714           state->mouse_ball = 0;
715           return True;
716         }
717       else
718         for (i=1; i <= state->count; i++)
719           {
720             float d = ((state->px[i] - rx) * (state->px[i] - rx) +
721                        (state->py[i] - ry) * (state->py[i] - ry));
722             float r = state->r[i];
723             if (d < r*r)
724               {
725                 state->mouse_ball = i;
726                 return True;
727               }
728           }
729       return True;
730     }
731   else if (event->xany.type == ButtonRelease)   /* drop the ball */
732     {
733       state->mouse_ball = 0;
734       return True;
735     }
736
737   return False;
738 }
739
740 static unsigned long
741 fluidballs_draw (Display *dpy, Window window, void *closure)
742 {
743   b_state *state = (b_state *) closure;
744   repaint_balls(state);
745   update_balls(state);
746   return state->delay;
747 }
748
749 static void
750 fluidballs_reshape (Display *dpy, Window window, void *closure, 
751                  unsigned int w, unsigned int h)
752 {
753 }
754
755 static void
756 fluidballs_free (Display *dpy, Window window, void *closure)
757 {
758   b_state *state = (b_state *) closure;
759   free (state);
760 }
761
762
763 static const char *fluidballs_defaults [] = {
764   ".background:         black",
765   ".foreground:         yellow",
766   ".textColor:          white",
767   "*mouseForeground:    white",
768   "*delay:              10000",
769   "*count:              300",
770   "*size:               25",
771   "*random:             True",
772   "*gravity:            0.01",
773   "*wind:               0.00",
774   "*elasticity:         0.97",
775   "*timeScale:          1.0",
776   "*shake:              True",
777   "*shakeThreshold:     0.015",
778   "*doubleBuffer:       True",
779 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
780   "*useDBE:             True",
781   "*useDBEClear:        True",
782 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
783   0
784 };
785
786 static XrmOptionDescRec fluidballs_options [] = {
787   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
788   { "-count",           ".count",       XrmoptionSepArg, 0 },
789   { "-size",            ".size",        XrmoptionSepArg, 0 },
790   { "-count",           ".count",       XrmoptionSepArg, 0 },
791   { "-gravity",         ".gravity",     XrmoptionSepArg, 0 },
792   { "-wind",            ".wind",        XrmoptionSepArg, 0 },
793   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
794   { "-shake",           ".shake",       XrmoptionNoArg, "True" },
795   { "-no-shake",        ".shake",       XrmoptionNoArg, "False" },
796   { "-random",          ".random",      XrmoptionNoArg, "True" },
797   { "-no-random",       ".random",      XrmoptionNoArg, "False" },
798   { "-nonrandom",       ".random",      XrmoptionNoArg, "False" },
799   { "-db",              ".doubleBuffer", XrmoptionNoArg,  "True" },
800   { "-no-db",           ".doubleBuffer", XrmoptionNoArg,  "False" },
801   { 0, 0, 0, 0 }
802 };
803
804
805 XSCREENSAVER_MODULE ("FluidBalls", fluidballs)