source: trunk/third/xscreensaver/hacks/bubbles.c @ 20148

Revision 20148, 34.4 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r20147, which included commits to RCS files with non-trunk default branches.
Line 
1/* bubbles.c - frying pan / soft drink in a glass simulation */
2
3/*$Id: bubbles.c,v 1.1.1.3 2004-02-23 18:07:59 ghudson 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
73extern void init_default_bubbles(void);
74extern int num_default_bubbles;
75extern char **default_bubbles[];
76static int drop_bubble( Bubble *bb );
77
78char *progclass = "Bubbles";
79
80char *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
94XrmOptionDescRec 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
113static Bubble **mesh;
114static int mesh_length;
115static int mesh_width;
116static int mesh_height;
117static int mesh_cells;
118
119static int **adjacent_list;
120
121static int screen_width;
122static int screen_height;
123static int screen_depth;
124static 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 */
129static Display *defdsp;
130static Window defwin;
131static Colormap defcmap;
132static Visual *defvisual;
133
134/* For simple mode only */
135static int bubble_min_radius;
136static int bubble_max_radius;
137static long *bubble_areas;
138static int *bubble_droppages;
139static GC draw_gc, erase_gc;
140
141#ifdef FANCY_BUBBLES
142static int num_bubble_pixmaps;
143static Bubble_Step **step_pixmaps;
144#endif
145
146/* Options stuff */
147#ifdef FANCY_BUBBLES
148static Bool simple = False;
149#else
150static Bool simple = True;
151#endif
152static Bool broken = False;
153static Bool quiet = False;
154static Bool threed = False;
155static Bool drop = False;
156static Bool trails = False;
157static int drop_dir;
158static int delay;
159
160/*
161 * To prevent forward references, some stuff is up here
162 */
163
164static long
165calc_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
178static void *
179xmalloc(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
192static void
193die_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
201static int
202null_bubble(Bubble *bb)
203/* Returns true if the pointer passed is NULL.  If not then this checks to
204see if the bubble is valid (i.e. the (x,y) position is valid and the magic
205number is set correctly.  This only a sanity check for debugging and is
206turned 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
247static void
248print_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
258static void
259add_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
282static void
283init_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
293static int
294cell_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
306static void
307mesh_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
314static int
315pixel_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
321static int
322verify_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
331static void
332print_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
344static void
345add_to_mesh(Bubble *bb)
346/* Add the given bubble to the mesh by sticking it on the front of the
347list.  bb is already allocated so no need to malloc() anything, just
348adjust 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
361static void
362print_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
375static void
376valid_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
389static int
390total_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
409static void
410calculate_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
433static void
434adjust_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
495static Bubble *
496new_bubble (void)
497/* Add a new bubble at some random position on the screen of the smallest
498size. */
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
527static void
528show_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
560static void
561hide_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
593static void
594delete_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
621static unsigned long
622ulongsqrint(int x)
623/* Saves ugly inline code */
624{
625  return ((unsigned long)x * (unsigned long)x);
626}
627
628static Bubble *
629get_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
681static void
682ldr_barf (void)
683{
684}
685#endif /* DEBUG */
686
687static long
688long_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
716static int
717weighted_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
732static int
733bubble_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
817static int
818merge_bubbles(Bubble *b1, Bubble *b2)
819/* These two bubbles merge into one.  If the first one wins out return
8201 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
891static void
892insert_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
976static void
977leave_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
991static int
992drop_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
1037static int
1038get_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
1063static void
1064pixmap_sort(Bubble_Step **head, int numelems)
1065/* Couldn't get qsort to work right with this so I wrote my own.  This puts
1066the 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
1090static int
1091extrapolate(int i1, int i2)
1092{
1093  return (i2 + (i2 - i1));
1094}
1095
1096static void
1097make_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
1174static void
1175make_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
1180changes 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
1218static void
1219default_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
1257static void
1258get_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
1315static void
1316init_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
1407static void
1408bubbles (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
1420void
1421screenhack (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
Note: See TracBrowser for help on using the repository browser.