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