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