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