http://www.jwz.org/xscreensaver/xscreensaver-5.07.tar.gz
[xscreensaver] / hacks / bubbles.c
1 /* bubbles.c - frying pan / soft drink in a glass simulation */
2
3 /*$Id: bubbles.c,v 1.30 2008/07/31 19:27:48 jwz Exp $*/
4
5 /*
6  *  Copyright (C) 1995-1996 James Macnicol
7  *
8  * Permission to use, copy, modify, distribute, and sell this software and its
9  * documentation for any purpose is hereby granted without fee, provided that
10  * the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation.  No representations are made about the suitability of this
13  * software for any purpose.  It is provided "as is" without express or 
14  * implied warranty.
15  */
16
17 /*
18  * I got my original inspiration for this by looking at the bottom of a 
19  * frying pan while something was cooking and watching the little bubbles
20  * coming off the bottom of the pan as the oil was boiling joining together
21  * to form bigger bubbles and finally to *pop* and disappear.  I had some
22  * time on my hands so I wrote this little xscreensaver module to imitate
23  * it.  Now that it's done it reminds me more of the bubbles you get in
24  * a glass of fizzy soft drink.....
25  *
26  * The problem seemed to be that the position/size etc. of all the bubbles
27  * on the screen had to be remembered and searched through to find when
28  * bubbles hit each other and combined.  To do this more efficiently, the
29  * window/screen is divided up into a square mesh of side length mesh_length
30  * and separate lists of bubbles contained in each cell of the mesh are
31  * kept.  Only the cells in the immediate vicinity of the bubble in question
32  * are searched.  This should make things more efficient although the whole
33  * thing seems to use up too much CPU, but then I'm using an ancient PC so
34  * perhaps it's not surprising .
35  * (Six months after I wrote the above I now have a Pentium with PCI graphics 
36  * and things are _much_ nicer.)
37  *
38  * Author:           James Macnicol 
39  * Internet E-mail : j-macnicol@adfa.edu.au
40  */
41
42 #ifdef HAVE_CONFIG_H
43 # include "config.h"
44 #endif
45
46 #include <math.h>
47 #include <limits.h>
48
49 #ifndef VMS
50 # include <sys/wait.h>
51 #else /* VMS */
52 # if __DECC_VER >= 50200000
53 #  include <sys/wait.h>
54 # endif
55 #endif /* VMS */
56
57 #ifdef HAVE_UNISTD_H
58 # include <unistd.h>
59 #endif
60
61 #include "screenhack.h"
62 #include "yarandom.h"
63 #include "bubbles.h"
64 #include "xpm-pixmap.h"
65
66 #if defined(HAVE_GDK_PIXBUF) || defined(HAVE_XPM)
67 # define FANCY_BUBBLES
68 #endif
69
70 /* 
71  * Public variables 
72  */
73
74 static const char *bubbles_defaults [] = {
75   ".background:         black",
76   ".foreground:         white",
77   "*fpsSolid:           true",
78   "*simple:             false",
79   "*broken:             false",
80   "*delay:              10000",
81   "*quiet:              false", 
82   "*mode:               float",
83   "*trails:             false",
84   "*3D:                 false",
85   0
86 };
87
88 static XrmOptionDescRec bubbles_options [] = {
89   { "-simple",          ".simple",      XrmoptionNoArg, "true" },
90 #ifdef FANCY_BUBBLES
91   { "-broken",          ".broken",      XrmoptionNoArg, "true" },
92 #endif
93   { "-quiet",           ".quiet",       XrmoptionNoArg, "true" },
94   { "-3D",          ".3D",      XrmoptionNoArg, "true" },
95   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
96   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
97   { "-drop",            ".mode",        XrmoptionNoArg, "drop" },
98   { "-rise",            ".mode",        XrmoptionNoArg, "rise" },
99   { "-trails",          ".trails",      XrmoptionNoArg, "true" },
100   { 0, 0, 0, 0 }
101 };
102
103 /* 
104  * Private variables 
105  */
106
107 struct state {
108   Display *dpy;
109   Window window;
110
111   Bubble **mesh;
112   int mesh_length;
113   int mesh_width;
114   int mesh_height;
115   int mesh_cells;
116
117   int **adjacent_list;
118
119   int screen_width;
120   int screen_height;
121   int screen_depth;
122   unsigned int default_fg_pixel, default_bg_pixel;
123
124   int bubble_min_radius;        /* For simple mode only */
125   int bubble_max_radius;
126   long *bubble_areas;
127   int *bubble_droppages;
128   GC draw_gc, erase_gc;
129
130 #ifdef FANCY_BUBBLES
131   int num_bubble_pixmaps;
132   Bubble_Step **step_pixmaps;
133 #endif
134
135   Bool simple;
136   Bool broken;
137   Bool quiet;
138   Bool threed;
139   Bool drop;
140   Bool trails;
141   int drop_dir;
142   int delay;
143 };
144
145 static int drop_bubble( struct state *st, Bubble *bb );
146
147 /* 
148  * To prevent forward references, some stuff is up here 
149  */
150
151 static long
152 calc_bubble_area(struct state *st, int r)
153 /* Calculate the area of a bubble of radius r */
154 {
155 #ifdef DEBUG
156   printf("%d %g\n", r,
157          10.0 * PI * (double)r * (double)r * (double)r);
158 #endif /* DEBUG */
159   if (st->threed)
160     return (long)(10.0 * PI * (double)r * (double)r * (double)r);
161   else
162     return (long)(10.0 * PI * (double)r * (double)r);
163 }
164
165 static void *
166 xmalloc(size_t size)
167 /* Safe malloc */
168 {
169   void *ret;
170
171   if ((ret = malloc(size)) == NULL) {
172     fprintf(stderr, "%s: out of memory\n", progname);
173     exit(1);
174   }
175   return ret;
176 }
177
178 #ifdef DEBUG
179 static void 
180 die_bad_bubble(Bubble *bb)
181 /* This is for use with GDB */
182 {
183   fprintf(stderr, "Bad bubble detected at 0x%x!\n", (int)bb);
184   exit(1);
185 }
186 #endif
187
188 static int
189 null_bubble(Bubble *bb)
190 /* Returns true if the pointer passed is NULL.  If not then this checks to
191 see if the bubble is valid (i.e. the (x,y) position is valid and the magic
192 number is set correctly.  This only a sanity check for debugging and is
193 turned off if DEBUG isn't set. */
194 {
195   if (bb == (Bubble *)NULL)
196     return 1;
197 #ifdef DEBUG
198   if ((bb->cell_index < 0) || (bb->cell_index > st->mesh_cells)) {
199     fprintf(stderr, "cell_index = %d\n", bb->cell_index);
200     die_bad_bubble(bb);
201   }
202   if (bb->magic != BUBBLE_MAGIC)  {
203     fprintf(stderr, "Magic = %d\n", bb->magic);
204     die_bad_bubble(bb);
205   }
206   if (st->simple) {
207     if ((bb->x < 0) || (bb->x > st->screen_width) ||
208         (bb->y < 0) || (bb->y > st->screen_height) ||
209         (bb->radius < st->bubble_min_radius) || (bb->radius >
210                                              st->bubble_max_radius)) {
211       fprintf(stderr,
212               "radius = %d, x = %d, y = %d, magic = %d, cell index = %d\n",
213               bb->radius, bb->x, bb->y, bb->magic, bb->cell_index);
214       die_bad_bubble(bb);  
215     }
216 #ifdef FANCY_BUBBLES
217   } else {
218     if ((bb->x < 0) || (bb->x > st->screen_width) ||
219         (bb->y < 0) || (bb->y > st->screen_height) ||
220         (bb->radius < st->step_pixmaps[0]->radius) || 
221         (bb->radius > st->step_pixmaps[st->num_bubble_pixmaps-1]->radius)) {
222       fprintf(stderr,
223               "radius = %d, x = %d, y = %d, magic = %d, cell index = %d\n",
224               bb->radius, bb->x, bb->y, bb->magic, bb->cell_index);
225       die_bad_bubble(bb);  
226     }
227 #endif
228   }
229 #endif /* DEBUG */
230   return 0;
231 }
232
233 #ifdef DEBUG
234 static void 
235 print_bubble_list(Bubble *bb)
236 /* Print list of where all the bubbles are.  For debugging purposes only. */
237 {
238   if (! null_bubble(bb)) {
239     printf("  (%d, %d) %d\n", bb->x, bb->y, bb->radius);
240     print_bubble_list(bb->next);
241   }
242 }
243 #endif /* DEBUG */
244
245 static void 
246 add_bubble_to_list(Bubble **list, Bubble *bb)
247 /* Take a pointer to a list of bubbles and stick bb at the head of the
248  list. */
249 {
250   Bubble *head = *list;
251
252   if (null_bubble(head)) {
253     bb->prev = (Bubble *)NULL;
254     bb->next = (Bubble *)NULL;
255   } else {
256     bb->next = head;
257     bb->prev = (Bubble *)NULL;
258     head->prev = bb;
259   }
260   *list = bb;
261 }
262
263
264 /* 
265  * Mesh stuff 
266  */
267
268
269 static void 
270 init_mesh (struct state *st)
271 /* Setup the mesh of bubbles */
272 {
273   int i;
274
275   st->mesh = (Bubble **)xmalloc(st->mesh_cells * sizeof(Bubble *));
276   for (i = 0; i < st->mesh_cells; i++)
277     st->mesh[i] = (Bubble *)NULL;
278 }
279
280 static int
281 cell_to_mesh(struct state *st, int x, int y)
282 /* convert cell coordinates to mesh index */
283 {
284 #ifdef DEBUG
285   if ((x < 0) || (y < 0)) {
286     fprintf(stderr, "cell_to_mesh: x = %d, y = %d\n", x, y);
287     exit(1);
288   }
289 #endif
290   return ((st->mesh_width * y) + x);
291 }
292
293 static void 
294 mesh_to_cell(struct state *st, int mi, int *cx, int *cy)
295 /* convert mesh index into cell coordinates */
296 {
297   *cx = mi % st->mesh_width;
298   *cy = mi / st->mesh_width;
299 }
300
301 static int
302 pixel_to_mesh(struct state *st, int x, int y)
303 /* convert screen coordinates into mesh index */
304 {
305   return cell_to_mesh(st, (x / st->mesh_length), (y / st->mesh_length));
306 }
307
308 static int
309 verify_mesh_index(struct state *st, int x, int y)
310 /* check to see if (x,y) is in the mesh */
311 {
312   if ((x < 0) || (y < 0) || (x >= st->mesh_width) || (y >= st->mesh_height))
313     return (-1);
314   return (cell_to_mesh(st, x, y));
315 }
316
317 #ifdef DEBUG
318 static void 
319 print_adjacents(int *adj)
320 /* Print a list of the cells calculated above.  For debugging only. */
321 {
322   int i;
323
324   printf("(");
325   for (i = 0; i < 8; i++)
326     printf("%d ", adj[i]);
327   printf(")\n");
328 }
329 #endif /* DEBUG */
330
331 static void 
332 add_to_mesh(struct state *st, Bubble *bb)
333 /* Add the given bubble to the mesh by sticking it on the front of the
334 list.  bb is already allocated so no need to malloc() anything, just
335 adjust pointers. */
336 {
337 #ifdef DEBUG
338   if (null_bubble(bb)) {
339     fprintf(stderr, "Bad bubble passed to add_to_mesh()!\n");
340     exit(1);
341   }
342 #endif /* DEBUG */
343
344   add_bubble_to_list(&st->mesh[bb->cell_index], bb);
345 }
346
347 #ifdef DEBUG
348 static void 
349 print_mesh (struct state *st)
350 /* Print the contents of the mesh */
351 {
352   int i;
353
354   for (i = 0; i < st->mesh_cells; i++) {
355     if (! null_bubble(st->mesh[i])) {
356       printf("Mesh cell %d\n", i);
357       print_bubble_list(st->mesh[i]);
358     }
359   }
360 }
361
362 static void 
363 valid_mesh (struct state *st)
364 /* Check to see if the mesh is Okay.  For debugging only. */
365 {
366   int i;
367   Bubble *b;
368
369   for (i = 0; i < st->mesh_cells; i++) {
370     b = st->mesh[i];
371     while (! null_bubble(b))
372       b = b->next;
373   }
374 }
375
376 static int
377 total_bubbles (struct state *st)
378 /* Count how many bubbles there are in total.  For debugging only. */
379 {
380   int rv = 0;
381   int i;
382   Bubble *b;
383
384   for (i = 0; i < st->mesh_cells; i++) {
385     b = st->mesh[i];
386     while (! null_bubble(b)) {
387       rv++;
388       b = b->next;
389     } 
390   }
391
392   return rv;
393 }
394 #endif /* DEBUG */
395
396 static void 
397 calculate_adjacent_list (struct state *st)
398 /* Calculate the list of cells adjacent to a particular cell for use
399    later. */
400 {
401   int i; 
402   int ix, iy;
403
404   st->adjacent_list = (int **)xmalloc(st->mesh_cells * sizeof(int *));
405   for (i = 0; i < st->mesh_cells; i++) {
406     st->adjacent_list[i] = (int *)xmalloc(9 * sizeof(int));
407     mesh_to_cell(st, i, &ix, &iy);
408     st->adjacent_list[i][0] = verify_mesh_index(st, --ix, --iy);
409     st->adjacent_list[i][1] = verify_mesh_index(st, ++ix, iy);
410     st->adjacent_list[i][2] = verify_mesh_index(st, ++ix, iy);
411     st->adjacent_list[i][3] = verify_mesh_index(st, ix, ++iy);
412     st->adjacent_list[i][4] = verify_mesh_index(st, ix, ++iy);
413     st->adjacent_list[i][5] = verify_mesh_index(st, --ix, iy);
414     st->adjacent_list[i][6] = verify_mesh_index(st, --ix, iy);
415     st->adjacent_list[i][7] = verify_mesh_index(st, ix, --iy);
416     st->adjacent_list[i][8] = i;
417   }
418 }
419
420 static void
421 adjust_areas (struct state *st)
422 /* Adjust areas of bubbles so we don't get overflow in weighted_mean() */
423 {
424   double maxvalue;
425   long maxarea;
426   long factor;
427   int i;
428
429 #ifdef FANCY_BUBBLES
430   if (st->simple)
431     maxarea = st->bubble_areas[st->bubble_max_radius+1];
432   else
433     maxarea = st->step_pixmaps[st->num_bubble_pixmaps]->area;
434 #else /* !FANCY_BUBBLES */
435   maxarea = st->bubble_areas[st->bubble_max_radius+1];
436 #endif /* !FANCY_BUBBLES */
437   maxvalue = (double)st->screen_width * 2.0 * (double)maxarea;
438   factor = (long)ceil(maxvalue / (double)LONG_MAX);
439   if (factor > 1) {
440     /* Overflow will occur in weighted_mean().  We must divide areas
441        each by factor so it will never do so. */
442 #ifdef FANCY_BUBBLES
443     if (st->simple) {
444       for (i = st->bubble_min_radius; i <= st->bubble_max_radius+1; i++) {
445         st->bubble_areas[i] /= factor;
446         if (st->bubble_areas[i] == 0)
447           st->bubble_areas[i] = 1;
448       }
449     } else {
450       for (i = 0; i <= st->num_bubble_pixmaps; i++) {
451 #ifdef DEBUG
452         printf("area = %ld", st->step_pixmaps[i]->area);
453 #endif /* DEBUG */
454         st->step_pixmaps[i]->area /= factor;
455         if (st->step_pixmaps[i]->area == 0)
456           st->step_pixmaps[i]->area = 1;
457 #ifdef DEBUG
458         printf("-> %ld\n", st->step_pixmaps[i]->area);
459 #endif /* DEBUG */
460       }
461     }
462 #else /* !FANCY_BUBBLES */
463     for (i = st->bubble_min_radius; i <= st->bubble_max_radius+1; i++) {
464       st->bubble_areas[i] /= factor;
465       if (st->bubble_areas[i] == 0)
466         st->bubble_areas[i] = 1;
467     }
468 #endif /* !FANCY_BUBBLES */
469   }
470 #ifdef DEBUG
471   printf("maxarea = %ld\n", maxarea);
472   printf("maxvalue = %g\n", maxvalue);
473   printf("LONG_MAX = %ld\n", LONG_MAX);
474   printf("factor = %ld\n", factor);
475 #endif /* DEBUG */
476 }
477
478 /* 
479  * Bubbles stuff 
480  */
481
482 static Bubble *
483 new_bubble (struct state *st)
484 /* Add a new bubble at some random position on the screen of the smallest
485 size. */
486 {
487   Bubble *rv = (Bubble *)xmalloc(sizeof(Bubble));
488
489   /* Can't use null_bubble() here since magic number hasn't been set */
490   if (rv == (Bubble *)NULL) {
491     fprintf(stderr, "Ran out of memory!\n");
492     exit(1);
493   }
494
495   if (st->simple) {
496     rv->radius = st->bubble_min_radius;
497     rv->area = st->bubble_areas[st->bubble_min_radius];
498 #ifdef FANCY_BUBBLES
499   } else {
500     rv->step = 0;
501     rv->radius = st->step_pixmaps[0]->radius;
502     rv->area = st->step_pixmaps[0]->area;
503 #endif /* FANCY_BUBBLES */
504   }
505   rv->visible = 0;
506   rv->magic = BUBBLE_MAGIC;
507   rv->x = random() % st->screen_width;
508   rv->y = random() % st->screen_height;
509   rv->cell_index = pixel_to_mesh(st, rv->x, rv->y);
510
511   return rv;
512 }
513
514 static void 
515 show_bubble(struct state *st, Bubble *bb)
516 /* paint the bubble on the screen */
517 {
518   if (null_bubble(bb)) {
519     fprintf(stderr, "NULL bubble passed to show_bubble\n");
520     exit(1);
521   }
522
523   if (! bb->visible) {
524     bb->visible = 1;
525
526     if (st->simple) {
527       XDrawArc(st->dpy, st->window, st->draw_gc, (bb->x - bb->radius),
528                (bb->y - bb->radius), bb->radius*2, bb->radius*2, 0,
529                360*64);  
530     } else {
531 #ifdef FANCY_BUBBLES
532       XSetClipOrigin(st->dpy, st->step_pixmaps[bb->step]->draw_gc, 
533                      (bb->x - bb->radius),
534                      (bb->y - bb->radius));
535       
536       XCopyArea(st->dpy, st->step_pixmaps[bb->step]->ball, st->window, 
537                 st->step_pixmaps[bb->step]->draw_gc,
538                 0, 0, (bb->radius * 2), 
539                 (bb->radius * 2),  
540                 (bb->x - bb->radius),
541                 (bb->y - bb->radius));
542 #endif /* FANCY_BUBBLES */
543     }
544   }
545 }
546
547 static void 
548 hide_bubble(struct state *st, Bubble *bb)
549 /* erase the bubble */
550 {
551   if (null_bubble(bb)) {
552     fprintf(stderr, "NULL bubble passed to hide_bubble\n");
553     exit(1);
554   }
555
556   if (bb->visible) {
557     bb->visible = 0;
558
559     if (st->simple) {
560       XDrawArc(st->dpy, st->window, st->erase_gc, (bb->x - bb->radius),
561                (bb->y - bb->radius), bb->radius*2, bb->radius*2, 0,
562                360*64);
563     } else {
564 #ifdef FANCY_BUBBLES
565       if (! st->broken) {
566         XSetClipOrigin(st->dpy, st->step_pixmaps[bb->step]->erase_gc, 
567                        (bb->x - bb->radius), (bb->y - bb->radius));
568         
569         XFillRectangle(st->dpy, st->window, st->step_pixmaps[bb->step]->erase_gc,
570                        (bb->x - bb->radius),
571                        (bb->y - bb->radius),
572                        (bb->radius * 2),
573                        (bb->radius * 2));
574       }
575 #endif /* FANCY_BUBBLES */
576     }
577   }
578 }
579
580 static void 
581 delete_bubble_in_mesh(struct state *st, Bubble *bb, int keep_bubble)
582 /* Delete an individual bubble, adjusting list of bubbles around it.
583    If keep_bubble is true then the bubble isn't actually deleted.  We
584    use this to allow bubbles to change mesh cells without reallocating,
585    (it needs this when two bubbles collide and the centre position is
586    recalculated, and this may stray over a mesh boundary). */
587 {
588   if ((!null_bubble(bb->prev)) && (!null_bubble(bb->next))) {
589     bb->prev->next = bb->next;
590     bb->next->prev = bb->prev;
591   } else if ((!null_bubble(bb->prev)) &&
592              (null_bubble(bb->next))) {
593     bb->prev->next = (Bubble *)NULL;
594     bb->next = st->mesh[bb->cell_index];
595   } else if ((null_bubble(bb->prev)) &&
596              (!null_bubble(bb->next))) {
597     bb->next->prev = (Bubble *)NULL;
598     st->mesh[bb->cell_index] = bb->next;
599     bb->next = st->mesh[bb->cell_index];
600   } else {
601     /* Only item on list */
602     st->mesh[bb->cell_index] = (Bubble *)NULL;
603   }              
604   if (! keep_bubble)
605     free(bb);
606 }
607
608 static unsigned long 
609 ulongsqrint(int x)
610 /* Saves ugly inline code */
611 {
612   return ((unsigned long)x * (unsigned long)x);
613 }
614
615 static Bubble *
616 get_closest_bubble(struct state *st, Bubble *bb)
617 /* Find the closest bubble touching the this bubble, NULL if none are
618    touching. */
619 {
620   Bubble *rv = (Bubble *)NULL;
621   Bubble *tmp;
622   unsigned long separation2, touchdist2;
623   int dx, dy;
624   unsigned long closest2 = ULONG_MAX;
625   int i;
626
627 #ifdef DEBUG 
628   if (null_bubble(bb)) {
629     fprintf(stderr, "NULL pointer 0x%x passed to get_closest_bubble()!", 
630             (int)bb);
631     exit(1);
632   }
633 #endif /* DEBUG */
634
635   for (i = 0; i < 9; i++) {
636     /* There is a bug here where bb->cell_index is negaitve.. */
637 #ifdef DEBUG
638     if ((bb->cell_index < 0) || (bb->cell_index >= st->mesh_cells)) {
639       fprintf(stderr, "bb->cell_index = %d\n", bb->cell_index);
640       exit(1);
641     }
642 #endif /* DEBUG */
643 /*    printf("%d,", bb->cell_index); */
644     if (st->adjacent_list[bb->cell_index][i] != -1) {
645       tmp = st->mesh[st->adjacent_list[bb->cell_index][i]];
646       while (! null_bubble(tmp)) {
647         if (tmp != bb) {
648           dx = tmp->x - bb->x;
649           dy = tmp->y - bb->y;
650           separation2 = ulongsqrint(dx) + ulongsqrint(dy);
651           /* Add extra leeway so circles _never_ overlap */
652           touchdist2 = ulongsqrint(tmp->radius + bb->radius + 2);
653           if ((separation2 <= touchdist2) && (separation2 <
654                                               closest2)) {
655             rv = tmp;
656             closest2 = separation2;
657           }
658         }
659         tmp = tmp->next;
660       }
661     }
662   }
663
664   return rv;
665 }
666
667 #ifdef DEBUG
668 static void
669 ldr_barf (struct state *st)
670 {
671 }
672 #endif /* DEBUG */
673
674 static long
675 long_div_round(long num, long dem)
676 {
677   long divvie, moddo;
678
679 #ifdef DEBUG
680   if ((num < 0) || (dem < 0)) {
681     fprintf(stderr, "long_div_round: %ld, %ld\n", num, dem);
682     ldr_barf();
683     exit(1);
684   }
685 #endif /* DEBUG */
686
687   divvie = num / dem;
688   moddo = num % dem;
689   if (moddo > (dem / 2))
690     ++divvie;
691
692 #ifdef DEBUG
693   if ((divvie < 0) || (moddo < 0)) {
694     fprintf(stderr, "long_div_round: %ld, %ld\n", divvie, moddo);
695     ldr_barf();
696     exit(1);
697   }
698 #endif /* DEBUG */
699
700   return divvie;
701 }
702
703 static int
704 weighted_mean(int n1, int n2, long w1, long w2)
705 /* Mean of n1 and n2 respectively weighted by weights w1 and w2. */
706 {
707 #ifdef DEBUG
708   if ((w1 <= 0) || (w2 <= 0)) {
709     fprintf(stderr,
710             "Bad weights passed to weighted_mean() - (%d, %d, %ld, %ld)!\n",
711             n1, n2, w1, w2);
712     exit(1);
713   }
714 #endif /* DEBUG */
715   return ((int)long_div_round((long)n1 * w1 + (long)n2 * w2,
716                            w1 + w2));
717 }
718
719 static int
720 bubble_eat(struct state *st, Bubble *diner, Bubble *food)
721 /* The diner eats the food.  Returns true (1) if the diner still exists */
722
723   int i;
724   int newmi;
725
726 #ifdef DEBUG
727   if ((null_bubble(diner)) || (null_bubble(food))) {
728     fprintf(stderr, "Bad bubbles passed to bubble_eat()!\n");
729     exit(1);
730   }
731 #endif /* DEBUG */
732
733   /* We hide the diner even in the case that it doesn't grow so that
734      if the food overlaps its boundary it is replaced. This could
735      probably be solved by letting bubbles eat others which are close
736      but not quite touching.  It's probably worth it, too, since we
737      would then not have to redraw bubbles which don't change in
738      size. */
739
740   hide_bubble(st, diner);
741   hide_bubble(st, food);
742   diner->x = weighted_mean(diner->x, food->x, diner->area, food->area);
743   diner->y = weighted_mean(diner->y, food->y, diner->area, food->area);
744   newmi = pixel_to_mesh(st, diner->x, diner->y);
745   diner->area += food->area;
746   delete_bubble_in_mesh(st, food, DELETE_BUBBLE);
747
748   if (st->drop) {
749         if ((st->simple) && (diner->area > st->bubble_areas[st->bubble_max_radius])) {
750           diner->area = st->bubble_areas[st->bubble_max_radius];
751         }
752 #ifdef FANCY_BUBBLES
753         if ((! st->simple) && (diner->area > st->step_pixmaps[st->num_bubble_pixmaps]->area)) {
754           diner->area = st->step_pixmaps[st->num_bubble_pixmaps]->area;
755         }
756 #endif /* FANCY_BUBBLES */
757   }
758   else {
759         if ((st->simple) && (diner->area > st->bubble_areas[st->bubble_max_radius])) {
760           delete_bubble_in_mesh(st, diner, DELETE_BUBBLE);
761           return 0;
762         }
763 #ifdef FANCY_BUBBLES
764         if ((! st->simple) && (diner->area > 
765                                            st->step_pixmaps[st->num_bubble_pixmaps]->area)) {
766           delete_bubble_in_mesh(st, diner, DELETE_BUBBLE);
767           return 0;
768         }
769 #endif /* FANCY_BUBBLES */
770   }
771
772   if (st->simple) {
773     if (diner->area > st->bubble_areas[diner->radius + 1]) {
774       /* Move the bubble to a new radius */
775       i = diner->radius;
776       while ((i < st->bubble_max_radius - 1) && (diner->area > st->bubble_areas[i+1]))
777                 ++i;
778       diner->radius = i;
779     }
780     show_bubble(st, diner);
781 #ifdef FANCY_BUBBLES
782   } else {
783     if (diner->area > st->step_pixmaps[diner->step+1]->area) {
784       i = diner->step;
785       while ((i < st->num_bubble_pixmaps - 1) && (diner->area > st->step_pixmaps[i+1]->area))
786                 ++i;
787       diner->step = i;
788       diner->radius = st->step_pixmaps[diner->step]->radius;
789     }
790     show_bubble(st, diner);
791 #endif /* FANCY_BUBBLES */
792   }
793
794   /* Now adjust locations and cells if need be */
795   if (newmi != diner->cell_index) {
796     delete_bubble_in_mesh(st, diner, KEEP_BUBBLE);
797     diner->cell_index = newmi;
798     add_to_mesh(st, diner);
799   }
800
801   return 1;
802 }
803
804 static int
805 merge_bubbles(struct state *st, Bubble *b1, Bubble *b2)
806 /* These two bubbles merge into one.  If the first one wins out return
807 1 else return 2.  If there is no winner (it explodes) then return 0 */
808 {
809   int b1size, b2size;
810
811   b1size = b1->area;
812   b2size = b2->area;
813
814 #ifdef DEBUG
815   if ((null_bubble(b1) || null_bubble(b2))) {
816     fprintf(stderr, "NULL bubble passed to merge_bubbles()!\n");
817     exit(1);
818   }
819 #endif /* DEBUG */
820
821   if (b1 == b2) {
822     hide_bubble(st, b1);
823     delete_bubble_in_mesh(st, b1, DELETE_BUBBLE);
824     return 0;
825   }
826
827   if (b1size > b2size) {
828     switch (bubble_eat(st, b1, b2)) {
829     case 0:
830       return 0;
831       break;
832     case 1:
833       return 1;
834       break;
835     default:
836       break;
837     }
838   } else if (b1size < b2size) {
839     switch (bubble_eat(st, b2, b1)) {
840     case 0:
841       return 0;
842       break;
843     case 1:
844       return 2;
845       break;
846     default:
847       break;
848     }
849   } else {
850     if ((random() % 2) == 0) {
851       switch (bubble_eat(st, b1, b2)) {
852       case 0:
853         return 0;
854         break;
855       case 1:
856         return 1;
857         break;
858       default:
859         break;
860       }
861     } else {
862       switch (bubble_eat(st, b2, b1)) {
863       case 0:
864         return 0;
865         break;
866       case 1:
867         return 2;
868         break;
869       default:
870         break;
871       }
872     }
873   }
874   fprintf(stderr, "An error occurred in merge_bubbles()\n");
875   exit(1);
876 }
877
878 static void 
879 insert_new_bubble(struct state *st, Bubble *tmp)
880 /* Calculates which bubbles are eaten when a new bubble tmp is
881    inserted.  This is called recursively in case when a bubble grows
882    it eats others.  Careful to pick out disappearing bubbles. */
883 {
884   Bubble *nextbub;
885   Bubble *touch;
886
887 #ifdef DEBUG
888   if (null_bubble(tmp)) {
889     fprintf(stderr, "Bad bubble passed to insert_new_bubble()!\n");
890     exit(1);
891   }
892 #endif /* DEBUG */
893   
894   nextbub = tmp;
895   touch = get_closest_bubble(st, nextbub);
896   if (null_bubble(touch))
897         return;
898
899   while (1) {
900
901         /* Merge all touching bubbles */
902         while (! null_bubble(touch)) {
903           switch (merge_bubbles(st, nextbub, touch)) {
904           case 2:
905                 /* touch ate nextbub and survived */
906                 nextbub = touch;
907                 break;
908           case 1:
909                 /* nextbub ate touch and survived */
910                 break;
911           case 0:
912                 /* somebody ate someone else but they exploded */
913                 nextbub = (Bubble *)NULL;
914                 break;
915           default:
916                 /* something went wrong */
917                 fprintf(stderr, "Error occurred in insert_new_bubble()\n");
918                 exit(1);
919           }
920         
921           /* Check to see if any bubble survived. */
922           if (null_bubble(nextbub))
923                 break;
924
925           /* Check to see if there are any other bubbles still in the area
926                  and if we need to do this all over again for them. */
927           touch = get_closest_bubble(st, nextbub);
928         }
929         
930         if (null_bubble(nextbub))
931           break;
932
933         /* Shift bubble down.  Break if we run off the screen. */
934         if (st->drop) {
935           if (drop_bubble( st, nextbub ) == -1)
936                 break;
937         }
938
939         /* Check to see if there are any other bubbles still in the area
940            and if we need to do this all over again for them. */
941         touch = get_closest_bubble(st, nextbub);
942         if (null_bubble(touch)) {
943           /* We also continue every so often if we're dropping and the bubble is at max size */
944           if (st->drop) {
945                 if (st->simple) {
946                   if ((nextbub->area >= st->bubble_areas[st->bubble_max_radius - 1]) && (random() % 2 == 0))
947                         continue;
948                 }
949 #ifdef FANCY_BUBBLES
950                 else {
951                   if ((nextbub->step >= st->num_bubble_pixmaps - 1) && (random() % 2 == 0))
952                         continue;
953                 }
954 #endif /* FANCY_BUBBLES */
955       }
956           break;
957         }
958
959   }
960 }
961
962
963 static void
964 leave_trail(struct state *st,  Bubble *bb ) 
965 {
966   Bubble *tmp;
967
968   tmp = new_bubble(st);
969   tmp->x = bb->x;
970   tmp->y = bb->y - ((bb->radius + 10) * st->drop_dir);
971   tmp->cell_index = pixel_to_mesh(st, tmp->x, tmp->y);
972   add_to_mesh(st, tmp);
973   insert_new_bubble(st, tmp);
974   show_bubble( st, tmp );       
975 }
976
977
978 static int
979 drop_bubble( struct state *st, Bubble *bb )
980 {
981   int newmi;
982
983   hide_bubble( st, bb );
984
985   if (st->simple)
986         (bb->y) += (st->bubble_droppages[bb->radius] * st->drop_dir);
987 #ifdef FANCY_BUBBLES
988   else
989         (bb->y) += (st->step_pixmaps[bb->step]->droppage * st->drop_dir);
990 #endif /* FANCY_BUBBLES */
991   if ((bb->y < 0) || (bb->y > st->screen_height)) {
992         delete_bubble_in_mesh( st, bb, DELETE_BUBBLE );
993         return -1;
994   }
995
996   show_bubble( st, bb );
997
998   /* Now adjust locations and cells if need be */
999   newmi = pixel_to_mesh(st, bb->x, bb->y);
1000   if (newmi != bb->cell_index) {
1001     delete_bubble_in_mesh(st, bb, KEEP_BUBBLE);
1002     bb->cell_index = newmi;
1003     add_to_mesh(st, bb);
1004   }
1005
1006   if (st->trails) {
1007         if (st->simple) {
1008           if ((bb->area >= st->bubble_areas[st->bubble_max_radius - 1]) && (random() % 2 == 0)) 
1009                 leave_trail( st, bb );
1010         }
1011 #ifdef FANCY_BUBBLES
1012         else { 
1013           if ((bb->step >= st->num_bubble_pixmaps - 1) && (random() % 2 == 0))
1014                 leave_trail( st, bb );
1015         }
1016 #endif /* FANCY_BUBBLES */
1017   }
1018
1019   return 0;
1020 }
1021
1022
1023 #ifdef DEBUG
1024 static int
1025 get_length_of_bubble_list(Bubble *bb)
1026 {
1027   Bubble *tmp = bb;
1028   int rv = 0;
1029
1030   while (! null_bubble(tmp)) {
1031     rv++;
1032     tmp = tmp->next;
1033   }
1034
1035   return rv;
1036 }
1037 #endif /* DEBUG */
1038
1039 /*
1040  * Pixmap stuff used regardless of whether file I/O is available.  Must
1041  * still check for XPM, though!
1042  */
1043
1044 #ifdef FANCY_BUBBLES
1045
1046 /*
1047  * Pixmaps without file I/O (but do have XPM)
1048  */
1049
1050 static void 
1051 pixmap_sort(Bubble_Step **head, int numelems)
1052 /* Couldn't get qsort to work right with this so I wrote my own.  This puts
1053 the numelems length array with first element at head into order of radius.
1054 */
1055 {
1056   Bubble_Step tmp;
1057   Bubble_Step *least = 0;
1058   int minradius = INT_MAX;
1059   int i;
1060
1061   for (i = 0; i < numelems; i++) {
1062     if (head[i]->radius < minradius) {
1063       least = head[i];
1064       minradius = head[i]->radius;
1065     }
1066   }
1067   if (*head != least) {
1068     memcpy(&tmp, least, sizeof(Bubble_Step));
1069     memcpy(least, *head, sizeof(Bubble_Step));
1070     memcpy(*head, &tmp, sizeof(Bubble_Step));
1071   }
1072
1073   if (numelems > 2)
1074     pixmap_sort(&head[1], numelems-1);
1075 }
1076
1077 static int
1078 extrapolate(int i1, int i2)
1079 {
1080   return (i2 + (i2 - i1));
1081 }
1082
1083 static void 
1084 make_pixmap_array(struct state *st, Bubble_Step *list)
1085 /* From a linked list of bubbles construct the array step_pixmaps */
1086 {
1087   Bubble_Step *tmp = list;
1088   int ind;
1089 #ifdef DEBUG
1090   int prevrad = -1;
1091 #endif
1092   
1093   if (list == (Bubble_Step *)NULL) {
1094     fprintf(stderr, "NULL list passed to make_pixmap_array\n");
1095     exit(1);
1096   }
1097
1098   st->num_bubble_pixmaps = 1;
1099   while(tmp->next != (Bubble_Step *)NULL) {
1100     tmp = tmp->next;
1101     ++st->num_bubble_pixmaps;
1102   }
1103
1104   if (st->num_bubble_pixmaps < 2) {
1105     fprintf(stderr, "Must be at least two bubbles in file\n");
1106     exit(1);
1107   }
1108
1109   st->step_pixmaps = (Bubble_Step **)xmalloc((st->num_bubble_pixmaps + 1) * 
1110                                          sizeof(Bubble_Step *));
1111
1112   /* Copy them blindly into the array for sorting. */
1113   ind = 0;
1114   tmp = list;
1115   do {
1116     st->step_pixmaps[ind++] = tmp;
1117     tmp = tmp->next;
1118   } while(tmp != (Bubble_Step *)NULL);
1119
1120   /* We make another bubble beyond the ones with pixmaps so that the final
1121      bubble hangs around and doesn't pop immediately.  It's radius and area
1122      are found by extrapolating from the largest two bubbles with pixmaps. */
1123
1124   st->step_pixmaps[st->num_bubble_pixmaps] = 
1125     (Bubble_Step *)xmalloc(sizeof(Bubble_Step));
1126   st->step_pixmaps[st->num_bubble_pixmaps]->radius = INT_MAX;
1127
1128   pixmap_sort(st->step_pixmaps, (st->num_bubble_pixmaps + 1));
1129
1130 #ifdef DEBUG
1131   if (st->step_pixmaps[st->num_bubble_pixmaps]->radius != INT_MAX) {
1132     fprintf(stderr, "pixmap_sort() screwed up make_pixmap_array\n");
1133   }
1134 #endif /* DEBUG */
1135
1136   st->step_pixmaps[st->num_bubble_pixmaps]->radius = 
1137     extrapolate(st->step_pixmaps[st->num_bubble_pixmaps-2]->radius,
1138                 st->step_pixmaps[st->num_bubble_pixmaps-1]->radius);
1139   st->step_pixmaps[st->num_bubble_pixmaps]->area = 
1140     calc_bubble_area(st, st->step_pixmaps[st->num_bubble_pixmaps]->radius);
1141   
1142
1143 #ifdef DEBUG
1144   /* Now check for correct order */
1145   for (ind = 0; ind < st->num_bubble_pixmaps; ind++) {
1146     if (prevrad > 0) {
1147       if (st->step_pixmaps[ind]->radius < prevrad) {
1148         fprintf(stderr, "Pixmaps not in ascending order of radius\n");
1149         exit(1);
1150       }
1151     }
1152     prevrad = st->step_pixmaps[ind]->radius;
1153   }
1154 #endif /* DEBUG */
1155
1156   /* Now populate the droppage values */
1157   for (ind = 0; ind < st->num_bubble_pixmaps; ind++)
1158           st->step_pixmaps[ind]->droppage = MAX_DROPPAGE * ind / st->num_bubble_pixmaps;
1159 }
1160
1161 static void
1162 make_pixmap_from_default(struct state *st, char **pixmap_data, Bubble_Step *bl)
1163 /* Read pixmap data which has been compiled into the program and a pointer
1164  to which has been passed. 
1165
1166  This is virtually copied verbatim from make_pixmap_from_file() above and
1167 changes made to either should be propagated onwards! */
1168 {
1169   XGCValues gcv;
1170
1171 #ifdef DEBUG
1172   if (pixmap_data == (char **)0) {
1173     fprintf(stderr, "make_pixmap_from_default(): NULL passed\n");
1174     exit(1);
1175   }
1176 #endif
1177
1178   if (bl == (Bubble_Step *)NULL) {
1179     fprintf(stderr, "NULL pointer passed to make_pixmap()\n");
1180     exit(1);
1181   }
1182
1183 #ifdef FANCY_BUBBLES
1184   {
1185     int w, h;
1186     bl->ball = xpm_data_to_pixmap (st->dpy, st->window, (char **) pixmap_data,
1187                                    &w, &h, &bl->shape_mask);
1188     bl->radius = MAX(w, h) / 2;
1189     bl->area = calc_bubble_area(st, bl->radius);
1190   }
1191 #endif /* FANCY_BUBBLES */
1192
1193   gcv.foreground = st->default_fg_pixel;
1194   gcv.function = GXcopy;
1195   bl->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
1196   XSetClipMask(st->dpy, bl->draw_gc, bl->shape_mask);
1197   
1198   gcv.foreground = st->default_bg_pixel;
1199   gcv.function = GXcopy;
1200   bl->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
1201   XSetClipMask(st->dpy, bl->erase_gc, bl->shape_mask);
1202 }
1203
1204 static void 
1205 default_to_pixmaps (struct state *st)
1206 /* Make pixmaps out of default ball data stored in bubbles_default.c */
1207 {
1208   int i;
1209   Bubble_Step *pixmap_list = (Bubble_Step *)NULL;
1210   Bubble_Step *newpix, *tmppix;
1211   char **pixpt;
1212
1213   init_default_bubbles();
1214
1215   for (i = 0; i < num_default_bubbles; i++) {
1216     pixpt = default_bubbles[i];
1217     newpix = (Bubble_Step *)xmalloc(sizeof(Bubble_Step));
1218     make_pixmap_from_default(st, pixpt, newpix);
1219     /* Now add to list */
1220     if (pixmap_list == (Bubble_Step *)NULL) {
1221       pixmap_list = newpix;
1222     } else {
1223       tmppix = pixmap_list;
1224       while (tmppix->next != (Bubble_Step *)NULL)
1225         tmppix = tmppix->next;
1226       tmppix->next = newpix;
1227     }
1228     newpix->next = (Bubble_Step *)NULL;
1229   }
1230   
1231   /* Finally construct step_pixmaps[] */
1232   make_pixmap_array(st, pixmap_list);
1233 }
1234
1235 #endif /* FANCY_BUBBLES */
1236
1237
1238 /* 
1239  * Main stuff 
1240  */
1241
1242
1243 static void 
1244 get_resources(struct state *st)
1245 /* Get the appropriate X resources and warn about any inconsistencies. */
1246 {
1247   Bool rise;
1248   XWindowAttributes xgwa;
1249   Colormap cmap;
1250   char *s;
1251   XGetWindowAttributes (st->dpy, st->window, &xgwa);
1252   cmap = xgwa.colormap;
1253
1254   st->threed = get_boolean_resource(st->dpy, "3D", "Boolean");
1255   st->quiet = get_boolean_resource(st->dpy, "quiet", "Boolean");
1256   st->simple = get_boolean_resource(st->dpy, "simple", "Boolean");
1257   /* Forbid rendered bubbles on monochrome displays */
1258   if ((mono_p) && (! st->simple)) {
1259     if (! st->quiet)
1260       fprintf(stderr,
1261               "Rendered bubbles not supported on monochrome displays\n");
1262     st->simple = True;
1263   }
1264   st->delay = get_integer_resource(st->dpy, "delay", "Integer");
1265
1266   s = get_string_resource (st->dpy, "mode", "Mode");
1267   rise = False;
1268   if (!s || !*s || !strcasecmp (s, "float"))
1269     ;
1270   else if (!strcasecmp (s, "rise"))
1271     rise = True;
1272   else if (!strcasecmp (s, "drop"))
1273     st->drop = True;
1274   else
1275     fprintf (stderr, "%s: bogus mode: \"%s\"\n", progname, s);
1276
1277   st->trails = get_boolean_resource(st->dpy, "trails", "Boolean");
1278   st->drop_dir = (st->drop ? 1 : -1);
1279   if (st->drop || rise)
1280         st->drop = 1;
1281
1282   st->default_fg_pixel = get_pixel_resource (st->dpy,
1283                                          cmap, "foreground", "Foreground");
1284   st->default_bg_pixel = get_pixel_resource (st->dpy,
1285                                          cmap, "background", "Background");
1286
1287   if (st->simple) {
1288     /* This is easy */
1289     st->broken = get_boolean_resource(st->dpy, "broken", "Boolean");
1290     if (st->broken)
1291       if (! st->quiet)
1292         fprintf(stderr, "-broken not available in simple mode\n");
1293   } else {
1294 #ifndef FANCY_BUBBLES
1295     st->simple = 1;
1296 #else  /* FANCY_BUBBLES */
1297     st->broken = get_boolean_resource(st->dpy, "broken", "Boolean");
1298 #endif /* FANCY_BUBBLES */
1299   }
1300 }
1301
1302 static void *
1303 bubbles_init (Display *dpy, Window window)
1304 {
1305   struct state *st = (struct state *) calloc (1, sizeof(*st));
1306   XGCValues gcv;
1307   XWindowAttributes xgwa;
1308   int i;
1309
1310   st->dpy = dpy;
1311   st->window = window;
1312
1313   get_resources(st);
1314
1315   XGetWindowAttributes (st->dpy, st->window, &xgwa);
1316
1317 #ifdef DEBUG
1318   printf("sizof(int) on this platform is %d\n", sizeof(int));
1319   printf("sizof(long) on this platform is %d\n", sizeof(long));
1320 #endif /* DEBUG */
1321
1322   st->screen_width = xgwa.width;
1323   st->screen_height = xgwa.height;
1324   st->screen_depth = xgwa.depth;
1325
1326   if (st->simple) {
1327     /* These are pretty much plucked out of the air */
1328     st->bubble_min_radius = (int)(0.006*(double)(MIN(st->screen_width, 
1329                                                  st->screen_height)));
1330     st->bubble_max_radius = (int)(0.045*(double)(MIN(st->screen_width,
1331                                                  st->screen_height)));
1332     /* Some trivial values */
1333     if (st->bubble_min_radius < 1)
1334       st->bubble_min_radius = 1;
1335     if (st->bubble_max_radius <= st->bubble_min_radius)
1336       st->bubble_max_radius = st->bubble_min_radius + 1;
1337
1338     st->mesh_length = (2 * st->bubble_max_radius) + 3;
1339
1340     /* store area of each bubble of certain radius as number of 1/10s of
1341        a pixel area.  PI is defined in <math.h> */
1342     st->bubble_areas = (long *)xmalloc((st->bubble_max_radius + 2) * sizeof(int));
1343     for (i = 0; i < st->bubble_min_radius; i++)
1344       st->bubble_areas[i] = 0;
1345     for (i = st->bubble_min_radius; i <= (st->bubble_max_radius+1); i++)
1346       st->bubble_areas[i] = calc_bubble_area(st, i);
1347
1348         /* Now populate the droppage values */
1349     st->bubble_droppages = (int *)xmalloc((st->bubble_max_radius + 2) * sizeof(int));
1350     for (i = 0; i < st->bubble_min_radius; i++)
1351       st->bubble_droppages[i] = 0;
1352     for (i = st->bubble_min_radius; i <= (st->bubble_max_radius+1); i++)
1353       st->bubble_droppages[i] = MAX_DROPPAGE * (i - st->bubble_min_radius) / (st->bubble_max_radius - st->bubble_min_radius);
1354
1355     st->mesh_length = (2 * st->bubble_max_radius) + 3;
1356   } else {
1357 #ifndef FANCY_BUBBLES
1358     fprintf(stderr,
1359             "Bug: simple mode code not set but FANCY_BUBBLES not defined\n");
1360     exit(1);
1361 #else  /* FANCY_BUBBLES */
1362     /* Make sure all #ifdef sort of things have been taken care of in
1363        get_resources(). */
1364     default_to_pixmaps(st);
1365
1366     /* Set mesh length */
1367     st->mesh_length = (2 * st->step_pixmaps[st->num_bubble_pixmaps-1]->radius) + 3;
1368 #endif /* FANCY_BUBBLES */
1369
1370     /* Am I missing something in here??? */
1371   }
1372
1373   st->mesh_width = (st->screen_width / st->mesh_length) + 1;
1374   st->mesh_height = (st->screen_height / st->mesh_length) + 1;
1375   st->mesh_cells = st->mesh_width * st->mesh_height;
1376   init_mesh(st);
1377
1378   calculate_adjacent_list(st);
1379
1380   adjust_areas(st);
1381
1382   /* Graphics contexts for simple mode */
1383   if (st->simple) {
1384     gcv.foreground = st->default_fg_pixel;
1385     st->draw_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
1386     gcv.foreground = st->default_bg_pixel;
1387     st->erase_gc = XCreateGC (st->dpy, st->window, GCForeground, &gcv);
1388   }
1389
1390   XClearWindow (st->dpy, st->window);
1391
1392 # ifndef FANCY_BUBBLES
1393   st->simple = True;
1394 # endif
1395
1396   return st;
1397 }
1398
1399 static unsigned long
1400 bubbles_draw (Display *dpy, Window window, void *closure)
1401 {
1402   struct state *st = (struct state *) closure;
1403   int i;
1404   for (i = 0; i < 5; i++)
1405     {
1406       Bubble *tmp = new_bubble(st);
1407       add_to_mesh(st, tmp);
1408       insert_new_bubble(st, tmp);
1409     }
1410   return st->delay;
1411 }
1412
1413
1414 static void
1415 bubbles_reshape (Display *dpy, Window window, void *closure, 
1416                  unsigned int w, unsigned int h)
1417 {
1418 }
1419
1420 static Bool
1421 bubbles_event (Display *dpy, Window window, void *closure, XEvent *event)
1422 {
1423   return False;
1424 }
1425
1426 static void
1427 bubbles_free (Display *dpy, Window window, void *closure)
1428 {
1429   struct state *st = (struct state *) closure;
1430   free (st);
1431 }
1432
1433 XSCREENSAVER_MODULE ("Bubbles", bubbles)