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