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 | |
---|
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 | |
---|