http://slackware.bholcomb.com/slackware/slackware-11.0/source/xap/xscreensaver/xscree...
[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,
98                   state->xgwa.width, state->font_height);
99   XDrawImageString (state->dpy, state->b, state->font_gc,
100                     0, state->xgwa.height - state->font_baseline,
101                     state->fps_str, strlen(state->fps_str));
102 }
103
104 /* Finds the origin of the window relative to the root window, by
105    walking up the window tree until it reaches the top.
106  */
107 static void
108 window_origin (Display *dpy, Window window, int *x, int *y)
109 {
110   XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen (dpy)),
111                          0, 0, x, y, &window);
112 }
113
114
115 /* Queries the window position to see if the window has moved or resized.
116    We poll this instead of waiting for ConfigureNotify events, because
117    when the window manager moves the window, only one ConfigureNotify
118    comes in: at the end of the motion.  If we poll, we can react to the
119    new position while the window is still being moved.  (Assuming the WM
120    does OpaqueMove, of course.)
121  */
122 static void
123 check_window_moved (b_state *state)
124 {
125   float oxmin = state->xmin;
126   float oxmax = state->xmax;
127   float oymin = state->ymin;
128   float oymax = state->ymax;
129   int wx, wy;
130   XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
131   window_origin (state->dpy, state->window, &wx, &wy);
132   state->xmin = wx;
133   state->ymin = wy;
134   state->xmax = state->xmin + state->xgwa.width;
135   state->ymax = state->ymin + state->xgwa.height - state->font_height;
136
137   if (state->dbuf && (state->ba))
138     {
139       if (oxmax != state->xmax || oymax != state->ymax)
140         {
141           XFreePixmap (state->dpy, state->ba);
142           state->ba = XCreatePixmap (state->dpy, state->window, 
143                                      state->xgwa.width, state->xgwa.height,
144                                      state->xgwa.depth);
145           XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
146                           state->xgwa.width, state->xgwa.height);
147           state->b = state->ba;
148         }
149     }
150   else 
151     {
152       /* Only need to erase the window if the origin moved */
153       if (oxmin != state->xmin || oymin != state->ymin)
154         XClearWindow (state->dpy, state->window);
155       else if (state->fps_p && oymax != state->ymax)
156         XFillRectangle (state->dpy, state->b, state->erase_gc,
157                         0, state->xgwa.height - state->font_height,
158                         state->xgwa.width, state->font_height);
159     }
160 }
161
162
163 /* Returns the position of the mouse relative to the root window.
164  */
165 static void
166 query_mouse (b_state *state, int *x, int *y)
167 {
168   Window root1, child1;
169   int mouse_x, mouse_y, root_x, root_y;
170   unsigned int mask;
171   if (XQueryPointer (state->dpy, state->window, &root1, &child1,
172                      &root_x, &root_y, &mouse_x, &mouse_y, &mask))
173     {
174       *x = root_x;
175       *y = root_y;
176     }
177   else
178     {
179       *x = -9999;
180       *y = -9999;
181     }
182 }
183
184 /* Re-pick the colors of the balls, and the mouse-ball.
185  */
186 static void
187 recolor (b_state *state)
188 {
189   if (state->fg.flags)
190     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg.pixel, 1, 0);
191   if (state->fg2.flags)
192     XFreeColors (state->dpy, state->xgwa.colormap, &state->fg2.pixel, 1, 0);
193
194   state->fg.flags  = DoRed|DoGreen|DoBlue;
195   state->fg.red    = 0x8888 + (random() % 0x8888);
196   state->fg.green  = 0x8888 + (random() % 0x8888);
197   state->fg.blue   = 0x8888 + (random() % 0x8888);
198
199   state->fg2.flags = DoRed|DoGreen|DoBlue;
200   state->fg2.red   = 0x8888 + (random() % 0x8888);
201   state->fg2.green = 0x8888 + (random() % 0x8888);
202   state->fg2.blue  = 0x8888 + (random() % 0x8888);
203
204   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg))
205     XSetForeground (state->dpy, state->draw_gc,  state->fg.pixel);
206
207   if (XAllocColor (state->dpy, state->xgwa.colormap, &state->fg2))
208     XSetForeground (state->dpy, state->draw_gc2, state->fg2.pixel);
209 }
210
211 /* Initialize the state structure and various X data.
212  */
213 static void *
214 fluidballs_init (Display *dpy, Window window)
215 {
216   int i;
217   float extx, exty;
218   b_state *state = (b_state *) calloc (1, sizeof(*state));
219   XGCValues gcv;
220
221   state->dpy = dpy;
222   state->window = window;
223   state->delay = get_integer_resource (dpy, "delay", "Integer");
224
225   check_window_moved (state);
226
227   state->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean");
228
229 # ifdef HAVE_COCOA      /* Don't second-guess Quartz's double-buffering */
230   state->dbuf = False;
231 # endif
232
233   if (state->dbuf)
234     {
235 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
236       state->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean");
237       if (state->dbeclear_p)
238         state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground);
239       else
240         state->b = xdbe_get_backbuffer (dpy, window, XdbeUndefined);
241       state->backb = state->b;
242 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
243
244       if (!state->b)
245         {
246           state->ba = XCreatePixmap (state->dpy, state->window, 
247                                      state->xgwa.width, state->xgwa.height,
248                                      state->xgwa.depth);
249           state->b = state->ba;
250         }
251     }
252   else
253     {
254       state->b = state->window;
255     }
256
257   /* Select ButtonRelease events on the external window, if no other app has
258      already selected it (only one app can select it at a time: BadAccess. */
259 #if 0
260   if (! (state->xgwa.all_event_masks & ButtonReleaseMask))
261     XSelectInput (state->dpy, state->window,
262                   state->xgwa.your_event_mask | ButtonReleaseMask);
263 #endif
264
265   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
266                                       "foreground", "Foreground");
267   gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap,
268                                       "background", "Background");
269   state->draw_gc = XCreateGC (state->dpy, state->b,
270                               GCForeground|GCBackground, &gcv);
271
272   gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
273                                       "mouseForeground", "MouseForeground");
274   state->draw_gc2 = XCreateGC (state->dpy, state->b,
275                                GCForeground|GCBackground, &gcv);
276
277   gcv.foreground = gcv.background;
278   state->erase_gc = XCreateGC (state->dpy, state->b,
279                                GCForeground|GCBackground, &gcv);
280
281
282   if (state->ba) 
283     XFillRectangle (state->dpy, state->ba, state->erase_gc, 0, 0, 
284                     state->xgwa.width, state->xgwa.height);
285
286   recolor (state);
287
288   extx = state->xmax - state->xmin;
289   exty = state->ymax - state->ymin;
290
291   state->count = get_integer_resource (dpy, "count", "Count");
292   if (state->count < 1) state->count = 20;
293
294   state->max_radius = get_float_resource (dpy, "size", "Size") / 2;
295   if (state->max_radius < 1.0) state->max_radius = 1.0;
296
297   state->random_sizes_p = get_boolean_resource (dpy, "random", "Random");
298
299   /* If the initial window size is too small to hold all these balls,
300      make fewer of them...
301    */
302   {
303     float r = (state->random_sizes_p
304                ? state->max_radius * 0.7
305                : state->max_radius);
306     float ball_area = M_PI * r * r;
307     float balls_area = state->count * ball_area;
308     float window_area = state->xgwa.width * state->xgwa.height;
309     window_area *= 0.75;  /* don't pack it completely full */
310     if (balls_area > window_area)
311       state->count = window_area / ball_area;
312   }
313
314   state->accx = get_float_resource (dpy, "wind", "Wind");
315   if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0;
316
317   state->accy = get_float_resource (dpy, "gravity", "Gravity");
318   if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01;
319
320   state->e = get_float_resource (dpy, "elasticity", "Elacitcity");
321   if (state->e < 0.2 || state->e > 1.0) state->e = 0.97;
322
323   state->tc = get_float_resource (dpy, "timeScale", "TimeScale");
324   if (state->tc <= 0 || state->tc > 10) state->tc = 1.0;
325
326   state->shake_p = get_boolean_resource (dpy, "shake", "Shake");
327   state->shake_threshold = get_float_resource (dpy, "shakeThreshold",
328                                                "ShakeThreshold");
329
330   state->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS");
331   if (state->fps_p)
332     {
333       XFontStruct *font;
334       char *fontname = get_string_resource (dpy, "font", "Font");
335       const char *def_font = "fixed";
336       if (!fontname || !*fontname) fontname = (char *)def_font;
337       font = XLoadQueryFont (dpy, fontname);
338       if (!font) font = XLoadQueryFont (dpy, def_font);
339       if (!font) exit(-1);
340       gcv.font = font->fid;
341       gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap,
342                                           "textColor", "Foreground");
343       state->font_gc = XCreateGC(dpy, state->b,
344                                  GCFont|GCForeground|GCBackground, &gcv);
345       state->font_height = font->ascent + font->descent;
346       state->font_baseline = font->descent;
347     }
348
349   state->m   = (float *) malloc (sizeof (*state->m)   * (state->count + 1));
350   state->r   = (float *) malloc (sizeof (*state->r)   * (state->count + 1));
351   state->vx  = (float *) malloc (sizeof (*state->vx)  * (state->count + 1));
352   state->vy  = (float *) malloc (sizeof (*state->vy)  * (state->count + 1));
353   state->px  = (float *) malloc (sizeof (*state->px)  * (state->count + 1));
354   state->py  = (float *) malloc (sizeof (*state->py)  * (state->count + 1));
355   state->opx = (float *) malloc (sizeof (*state->opx) * (state->count + 1));
356   state->opy = (float *) malloc (sizeof (*state->opy) * (state->count + 1));
357
358   for (i=1; i<=state->count; i++)
359     {
360       state->px[i] = frand(extx) + state->xmin;
361       state->py[i] = frand(exty) + state->ymin;
362       state->vx[i] = frand(0.2) - 0.1;
363       state->vy[i] = frand(0.2) - 0.1;
364
365       state->r[i] = (state->random_sizes_p
366                      ? ((0.2 + frand(0.8)) * state->max_radius)
367                      : state->max_radius);
368       /*state->r[i] = pow(frand(1.0), state->sizegamma) * state->max_radius;*/
369
370       /* state->m[i] = pow(state->r[i],2) * M_PI; */
371       state->m[i] = pow(state->r[i],3) * M_PI * 1.3333;
372     }
373
374   memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1));
375   memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1));
376
377   return state;
378 }
379
380
381 /* Messes with gravity: permute "down" to be in a random direction.
382  */
383 static void
384 shake (b_state *state)
385 {
386   float a = state->accx;
387   float b = state->accy;
388   int i = random() % 4;
389
390   switch (i)
391     {
392     case 0:
393       state->accx = a;
394       state->accy = b;
395       break;
396     case 1:
397       state->accx = -a;
398       state->accy = -b;
399       break;
400     case 2:
401       state->accx = b;
402       state->accy = a;
403       break;
404     case 3:
405       state->accx = -b;
406       state->accy = -a;
407       break;
408     default:
409       abort();
410       break;
411     }
412
413   state->time_since_shake = 0;
414   recolor (state);
415 }
416
417
418 /* Look at the current time, and update state->time_since_shake.
419    Draw the FPS display if desired.
420  */
421 static void
422 check_wall_clock (b_state *state, float max_d)
423 {
424   state->frame_count++;
425   
426   if (state->time_tick++ > 20)  /* don't call gettimeofday() too often -- it's slow. */
427     {
428       struct timeval now;
429 # ifdef GETTIMEOFDAY_TWO_ARGS
430       struct timezone tzp;
431       gettimeofday(&now, &tzp);
432 # else
433       gettimeofday(&now);
434 # endif
435
436       if (state->last_time.tv_sec == 0)
437         state->last_time = now;
438
439       state->time_tick = 0;
440       if (now.tv_sec == state->last_time.tv_sec)
441         return;
442
443       state->time_since_shake += (now.tv_sec - state->last_time.tv_sec);
444
445       if (state->fps_p) 
446         {
447           float elapsed = ((now.tv_sec  + (now.tv_usec  / 1000000.0)) -
448                            (state->last_time.tv_sec + (state->last_time.tv_usec / 1000000.0)));
449           float fps = state->frame_count / elapsed;
450           float cps = state->collision_count / elapsed;
451           
452           sprintf (state->fps_str, 
453                    " FPS: %.2f  Collisions: %.3f/frame  Max motion: %.3f",
454                    fps, 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:          yellow",
767   "*mouseForeground:    white",
768   ".font:               -*-helvetica-*-r-*-*-*-180-*-*-p-*-*-*",
769   "*delay:              10000",
770   "*count:              300",
771   "*size:               25",
772   "*random:             True",
773   "*gravity:            0.01",
774   "*wind:               0.00",
775   "*elasticity:         0.97",
776   "*timeScale:          1.0",
777   "*doFPS:              False",
778   "*shake:              True",
779   "*shakeThreshold:     0.015",
780   "*doubleBuffer:       True",
781 #ifdef HAVE_DOUBLE_BUFFER_EXTENSION
782   "*useDBE:             True",
783   "*useDBEClear:        True",
784 #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */
785   0
786 };
787
788 static XrmOptionDescRec fluidballs_options [] = {
789   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
790   { "-count",           ".count",       XrmoptionSepArg, 0 },
791   { "-size",            ".size",        XrmoptionSepArg, 0 },
792   { "-count",           ".count",       XrmoptionSepArg, 0 },
793   { "-gravity",         ".gravity",     XrmoptionSepArg, 0 },
794   { "-wind",            ".wind",        XrmoptionSepArg, 0 },
795   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
796   { "-fps",             ".doFPS",       XrmoptionNoArg, "True" },
797   { "-no-fps",          ".doFPS",       XrmoptionNoArg, "False" },
798   { "-shake",           ".shake",       XrmoptionNoArg, "True" },
799   { "-no-shake",        ".shake",       XrmoptionNoArg, "False" },
800   { "-random",          ".random",      XrmoptionNoArg, "True" },
801   { "-no-random",       ".random",      XrmoptionNoArg, "False" },
802   { "-nonrandom",       ".random",      XrmoptionNoArg, "False" },
803   { "-db",              ".doubleBuffer", XrmoptionNoArg,  "True" },
804   { "-no-db",           ".doubleBuffer", XrmoptionNoArg,  "False" },
805   { 0, 0, 0, 0 }
806 };
807
808
809 XSCREENSAVER_MODULE ("Fluidballs", fluidballs)