1 /*
2   explosions.c
3 
4   A simplified 3D world with alpha-blended objects that look kind of like
5   fireworks or sparks.  Also has fun screen effects.
6 
7   Run with "--help" for runtime keyboard and mouse controls.
8   See COPYING.txt for license!
9 
10 
11   by Bill Kendrick
12   bill@newbreedsoftware.com
13   http://www.newbreedsoftware.com/bill/
14 
15   July 30, 2001 - October 6, 2001
16   July 31, 2005
17 */
18 
19 
20 #include <stdio.h>     /* For fprintf()'ing errors & sprintf()'ing filenames */
21 #include <stdlib.h>    /* For exit(), rand()/srand() and qsort() functions */
22 #include <math.h>      /* For cos() and sin() trig. functions */
23 #include <SDL.h>       /* For graphics init., blitting and event functions */
24 #include <SDL_image.h> /* For loading PNG-format image files */
25 
26 
27 #define VERSION "2001.10.06"
28 
29 
30 /* Constants: */
31 
32 #define FPS 30              /* Max frames-per-second to display */
33 #define NUM_EXPLOSIONS 500  /* Max number of mouse-based explosions to track */
34 #define MAX_BITS 1000       /* Max number of bits we can cue/render */
35 
36 
37 #define F_WIDTH 640         /* Window width */
38 #define F_HEIGHT 480        /* Window height */
39 
40 #define H_WIDTH (F_WIDTH / 2)    /* Horizontal center of window */
41 #define H_HEIGHT (F_HEIGHT / 2)  /* Vertical center of window */
42 
43 #define BURST_PER_FRAME 20  /* How many to shoot out of mouse */
44 #define BURST_SPEED 5       /* How far to shoot out of mouse */
45 #define GRAVITY 0.50        /* Affects of gravity (per frame) */
46 
47 #define DISTANCE 500.0      /* Distance (Z) to the 3D space's origin */
48 #define ASPECT 400.0        /* When doing "screen_x = (x / z) + (width / 2)"
49 			       and "screen_y = (y / z) + (height / 2)"
50 			       calculations, this scales down the effect of
51 			       the "Z" value.  Changing it can cause a nifty
52 			       'fish-eye'-lens effect! :) */
53 
54 
55 /* Effect toggles' possible values: */
56 
57 enum { FALSE, TRUE };
58 enum { NOZOOM, ZOOMIN, ZOOMOUT, NUM_ZOOM_MODES };
59 
60 
61 /* Effect toggles: */
62 
63 int fade = FALSE;           /* Toggle with [F] key */
64 int zoom = NOZOOM;          /* Change with [Z] key */
65 int rotate = FALSE;         /* Toggle with [R] key */
66 int blur = FALSE;           /* Toggle with [B] key */
67 int dissolve = FALSE;       /* Toggle with [D] key */
68 int heat = FALSE;           /* Toggle with [H] key */
69 
70 
71 /* Number of "spark#.png" files available, and human-readable names: */
72 
73 enum { YELLOW, GREEN, CYAN, BLUE, RAINBOW, NUMS, PEPPERS, NUM_BITMAPS };
74 
75 
76 /* Where in the PNGs each of the sprites begin, and their sizes: */
77 /* Probably almost easier to calculate, but we're drawing so many per frame! */
78 
79 int src_start[9] = {0, 3, 8, 15, 24, 35, 48, 63, 80};
80 int src_width[9] = {3, 5, 7, 9, 11, 13, 15, 17, 19};
81 
82 
83 /* Types: */
84 
85 /* Explosions that come out of the mouse, and which we must track, move,
86  * and, in the end, destroy: */
87 
88 typedef struct explosion_type {
89   int alive, time, c;   /* Is the explosion alive?  How old?  Color? */
90   float x, y, z;        /* X,Y,Z coordinates in 3D space */
91   float xm, ym, zm;     /* Speed/direction (eg, x < 0 == moving left) */
92 } explosion_type;
93 
94 
95 /* Each bit (which will be queued, sorted, and then rendered each frame) */
96 
97 typedef struct bit_type {
98   int x, y, z, c;       /* Screen X and Y coordinates.
99 			   Z corresponds to size (which sprite to draw)
100 			   and also is used when sorting (closer objects
101 			   are drawn last, so that they overlap the
102 			   farther ones */
103 } bit_type;
104 
105 
106 /* Globals */
107 
108 SDL_Surface * screen;   /* Window we're drawing into */
109 SDL_Surface * spark[NUM_BITMAPS];   /* Sprites (loaded from the PNG files) */
110 explosion_type explosions[NUM_EXPLOSIONS];   /* Explosions to track */
111 bit_type bits[MAX_BITS];  /* Queue for bits (sorted, then rendered) */
112 int num_bits;  /* How many bits in the queue */
113 float anglex, angley;  /* Angle to rotate world (controlled w/ arrow keys) */
114 float cos_anglex, sin_anglex,  /* When world-rotation angles change, these */
115       cos_angley, sin_angley;  /* are recalculated, so that trig functions
116 				  are called only a few times per frame,
117 				  rather than once for EVERY object drawn! */
118 
119 
120 /* Local function prototypes: */
121 
122 void event_loop(void);                           /* Main event loop */
123 void setup_sdl(void);                            /* Open window, load PNG */
124 void init(void);                                 /* Init variables */
125 void add_explosion(float x, float y, float z,    /* Add expl-bit to array */
126 		   float xm, float ym, float zm,
127 		   int time, int color);
128 void calc3d(float x, float y, float z,           /* Convert 2D to 3D */
129 	    int * sx, int * sy, int * ss);
130 void recalctrig(void);                           /* Re-COS/SIN after rotate */
131 void queuebit(float x, float y, float z, int c); /* Convert object for queue */
132 void addbit(int x, int y, int z, int c);         /* Add object to queue */
133 void sortbits(void);                             /* Sort queue */
134 int compare_bits(const void * b1, const void * b2);  /* (For sorting) */
135 void drawbits(void);                             /* Draw queue */
136 void fadeout(void);                              /* Fade effect */
137 void zoomout(int zoom_x, int zoom_y);            /* Zoom-out effect */
138 void zoomin(int zoom_x, int zoom_y);             /* Zoom-in effect */
139 void rotatescreen(int angle);                    /* Rotation effect */
140 void blurscreen();                               /* Blur effect */
141 void dissolvescreen();                           /* Dissolve effect */
142 void heatscreen();                               /* Heat (flame) effect */
143 Uint32 getpixel(SDL_Surface * surface,           /* (For effects) */
144 		int x, int y);
145 void putpixel(SDL_Surface * surface,             /* (For effects) */
146 	      int x, int y, Uint32 pixel);
147 
148 
149 /* --- MAIN --- */
150 
main(int argc,char * argv[])151 int main(int argc, char * argv[])
152 {
153   int i;
154 
155 
156   /* Deal with any command-line options: */
157 
158   for (i = 1; i < argc; i++)
159     {
160       if (strcmp(argv[i], "--version") ==0 ||
161 	  strcmp(argv[i], "-v") == 0)
162 	{
163 	  /* Show version: */
164 
165 	  printf("\n\"explosions\" demo version " VERSION "\n");
166 	  printf("By Bill Kendrick (bill@newbreedsoftware.com) 2001\n\n");
167 
168 
169 	  /* Quit happily: */
170 
171 	  exit(0);
172 	}
173       else if (strcmp(argv[i], "--help") ==0 ||
174 	       strcmp(argv[i], "-h") == 0)
175 	{
176 	  /* Show help (runtime controls): */
177 
178 	  printf("\n\"explosions\" demo help\n"
179 		 "Keyboard:\n"
180 		 "  ARROWS  - Rotate screen\n"
181 		 "  F       - Toggle 'fade' effect\n"
182 		 "  Z       - Switch to next 'zoom' effect\n"
183 		 "  R       - Toggle 'rotation' effect\n"
184 		 "  B       - Toggle 'blur' effect\n"
185 		 "  D       - Toggle 'dissolve' effect\n"
186 		 "  H       - Toggle 'heat' (fire) effect\n"
187 		 "  KEYPAD  - Move zoom center\n"
188 		 "  PGUP/DN - Change rotation angle\n"
189 		 "  ESCAPE  - Quit\n\n");
190 
191 
192 	  /* Quit happily: */
193 
194 	  exit(0);
195 	}
196       else
197 	{
198 	  /* Print usage: */
199 
200 	  fprintf(stderr, "\n\"explosions\" demo Usage:\n");
201 	  fprintf(stderr, "%s [--version | --help]\n\n", argv[0]);
202 
203 
204 	  /* And quit with an error: */
205 
206 	  exit(1);
207 	}
208     }
209 
210 
211   /* Open window, create surface, load PNG images: */
212 
213   setup_sdl();
214 
215 
216   /* Clear tracked explosions array and other globals: */
217 
218   init();
219 
220 
221   /* MAIN EVENT LOOP! */
222 
223   event_loop();
224 
225 
226   /* All done... */
227 
228   SDL_Quit();
229 
230   return(0);
231 }
232 
233 
234 /* Event loop: */
235 
event_loop(void)236 void event_loop(void)
237 {
238   int i, done, count;
239   int mouse_down, mouse_x, mouse_y;
240   int left_down, right_down, up_down, down_down;
241   int kpleft_down, kpright_down, kpup_down, kpdown_down;
242   int pgdn_down, pgup_down;
243   int zoom_x, zoom_y, rotation_angle;
244   int color;
245   Uint32 now, then;
246   SDL_Event event;
247   SDLKey key;
248 
249 
250   /* No keys or mouse buttons have been pressed yet... */
251 
252   mouse_down = 0;
253   mouse_x = 0;
254   mouse_y = 0;
255 
256   left_down = 0;
257   right_down = 0;
258   up_down = 0;
259   down_down = 0;
260 
261   pgdn_down = 0;
262   pgup_down = 0;
263 
264   kpleft_down = 0;
265   kpright_down = 0;
266   kpup_down = 0;
267   kpdown_down = 0;
268 
269 
270   /* Reset effect variables: */
271 
272   zoom_x = H_WIDTH;
273   zoom_y = H_HEIGHT;
274   rotation_angle = 5;
275 
276 
277   /* Reset global counter */
278 
279   count = 0;
280 
281 
282   /* Current 'drawing' color (changed with BUTTON-3 clicks) */
283 
284   color = 0;
285 
286 
287   /* MAIN EVENT LOOP IS HERE! */
288 
289   done = 0;  /* (We haven't decided to quit yet) */
290 
291   do
292     {
293       /* Get the current time (used at the end of the loop, just before
294        * the while, to make sure framerate doesn't exceed chosen FPS! */
295 
296       then = SDL_GetTicks();
297 
298 
299       /* Increase global counter */
300 
301       count += 2;
302 
303 
304       /* Handle events: */
305 
306       while (SDL_PollEvent(&event))
307 	{
308           if (event.type == SDL_QUIT)
309             {
310               /* Quit request! (eg, the window's "close" button was clicked) */
311 
312               done = 1;
313             }
314 	  else if (event.type == SDL_KEYDOWN)
315 	    {
316               /* A keypress! */
317 
318               key = event.key.keysym.sym;
319 
320 	      if (key == SDLK_ESCAPE)
321 		done = 1;         /* ESCAPE - quit! */
322 	      else if (key == SDLK_LEFT)
323 		left_down = 1;    /* LEFT - record that left is pressed */
324 	      else if (key == SDLK_RIGHT)
325 		right_down = 1;   /* (ditto for RIGHT) */
326 	      else if (key == SDLK_UP)
327 		up_down = 1;      /* (ditto for UP) */
328 	      else if (key == SDLK_DOWN)
329 		down_down = 1;    /* (ditto for DOWN) */
330 	      else if (key == SDLK_PAGEDOWN)
331 		pgdn_down = 1;    /* (ditto for PAGEDOWN) */
332 	      else if (key == SDLK_PAGEUP)
333 		pgup_down = 1;    /* (ditto for PAGEUP) */
334 	      else if (key == SDLK_KP4)
335 		kpleft_down = 1;  /* (ditto for KPLEFT) */
336 	      else if (key == SDLK_KP6)
337 		kpright_down = 1; /* (ditto for KPRIGHT) */
338 	      else if (key == SDLK_KP8)
339 		kpup_down = 1;    /* (ditto for KPUP) */
340 	      else if (key == SDLK_KP2)
341 		kpdown_down = 1;  /* (ditto for KPDOWN) */
342 	      else if (key == SDLK_f)
343 		fade = !fade;     /* F - Toggle 'fade' effect */
344 	      else if (key == SDLK_z)
345 	        {
346 		  /* Z - Switch 'zoom' mode */
347 
348 	          zoom = (zoom + 1) % NUM_ZOOM_MODES;
349 		}
350 	      else if (key == SDLK_r)
351 		rotate = !rotate; /* R - Toggle 'rotation' effect */
352 	      else if (key == SDLK_b)
353 		blur = !blur;     /* B - Toggle 'blur' effect */
354 	      else if (key == SDLK_d)
355 		dissolve = !dissolve; /* D - Toggle 'dissolve' effect */
356               else if (key == SDLK_h)
357                 heat = !heat;     /* H - Toggle 'heat' (fire) effect */
358 	    }
359 	  else if (event.type == SDL_KEYUP)
360 	    {
361 	      /* A key-release! */
362 
363 	      key = event.key.keysym.sym;
364 
365 	      if (key == SDLK_LEFT)
366 		left_down = 0;  /* LEFT - record that left was released */
367 	      else if (key == SDLK_RIGHT)
368 		right_down = 0; /* (ditto for RIGHT) */
369 	      else if (key == SDLK_UP)
370 		up_down = 0;    /* (ditto for UP) */
371 	      else if (key == SDLK_DOWN)
372 		down_down = 0;  /* (ditto for DOWN) */
373               else if (key == SDLK_PAGEDOWN)
374                 pgdn_down = 0;  /* (ditto for PAGEDOWN) */
375 	      else if (key == SDLK_PAGEUP)
376 		pgup_down = 0;  /* (ditto for PAGEUP) */
377 	      else if (key == SDLK_KP4)
378 		kpleft_down = 0;  /* (ditto for KPLEFT) */
379 	      else if (key == SDLK_KP6)
380 		kpright_down = 0; /* (ditto for KPRIGHT) */
381 	      else if (key == SDLK_KP8)
382 		kpup_down = 0;    /* (ditto for KPUP) */
383 	      else if (key == SDLK_KP2)
384 		kpdown_down = 0;  /* (ditto for KPDOWN) */
385 	    }
386 	  else if (event.type == SDL_MOUSEBUTTONDOWN)
387 	    {
388 	      /* A mouse button was pressed, make note! */
389 
390 	      mouse_down = 1;
391 
392 
393 	      /* If it's button-3, change the 'drawing color' */
394 
395 	      if (event.button.button == 3)
396 		color = ((color + 1) % NUM_BITMAPS);
397 
398 
399 	      /* Notice where the mouse was when user clicked: */
400 
401 	      mouse_x = event.button.x;
402 	      mouse_y = event.button.y;
403 	    }
404 	  else if (event.type == SDL_MOUSEBUTTONUP)
405 	    {
406 	      /* A mouse button was released, make note! */
407 
408 	      mouse_down = 0;
409 	    }
410 	  else if (event.type == SDL_MOUSEMOTION)
411 	    {
412 	      /* Mouse was moved! */
413 
414 	      /* Notice where the mouse is now: (for all we know, the
415 	       * mouse button is still being held, so we should draw
416 	       * at the new location!) */
417 
418               mouse_x = event.motion.x;
419 	      mouse_y = event.motion.y;
420 	    }
421 	}
422 
423 
424       /* Handle rotation keys: */
425 
426       if (left_down)
427 	{
428 	  /* LEFT key is (still) depressed.  Rotate screen accordingly. */
429 
430 	  anglex = anglex + 5;
431 
432 
433 	  /* Recalculate trig. values ("cos_angelx", etc.) */
434 
435 	  recalctrig();
436 	}
437 
438       if (right_down)
439 	{
440           /* (ditto for RIGHT) */
441 
442 	  anglex = anglex - 5;
443 	  recalctrig();
444 	}
445 
446       if (up_down)
447 	{
448 	  /* (ditto for UP) */
449 
450 	  angley = angley + 5;
451 	  recalctrig();
452 	}
453 
454       if (down_down)
455 	{
456           /* (ditto for DOWN) */
457 
458 	  angley = angley - 5;
459 	  recalctrig();
460 	}
461 
462       /* NOTE: The above is _not_ an "if / else if / else if ..." block!
463        * Since two keys can be pressed at once (say, LEFT and UP), we want
464        * to be able to rotate both ways at the same time (eg, diagonal!) :) */
465 
466 
467       /* Handle interactive effects controls keys: */
468 
469       /* (Rotation effect) */
470 
471       if (pgup_down)
472         {
473 	  /* PAGEUP key is (still) depressed.  Change rotation effect angle. */
474 
475 	  rotation_angle = (rotation_angle - 2) % 360;
476         }
477 
478       if (pgdn_down)
479         {
480 	  /* (ditto for PAGEDOWN) */
481 
482 	  rotation_angle = (rotation_angle + 2) % 360;
483 	}
484 
485 
486       /* (Zoom effect) */
487 
488       if (kpleft_down)
489         {
490 	  /* KEYPAD_LEFT key is (still) depressed.  Move zoom effect. */
491 
492 	  zoom_x--;
493         }
494 
495       if (kpright_down)
496         {
497 	  /* (ditto for KEYPAD_RIGHT) */
498 
499 	  zoom_x++;
500         }
501 
502       if (kpup_down)
503         {
504 	  /* (ditto for KEYPAD_UP) */
505 
506 	  zoom_y--;
507         }
508 
509       if (kpdown_down)
510         {
511 	  /* (ditto for KEYPAD_DOWN) */
512 
513 	  zoom_y++;
514         }
515 
516 
517       /* Add explosion bits: */
518 
519       if (mouse_down)
520 	{
521 	  /* Mouse is (still being) clicked, add a few explosion bits: */
522 
523 	  for (i = 0; i < BURST_PER_FRAME; i++)
524 	    {
525 	      add_explosion(mouse_x - H_WIDTH,   /* Starting X */
526 			    mouse_y - H_HEIGHT,  /* Starting Y */
527 			    0,                   /* Starting Z */
528 
529 			    /* Starting XM (random left/right) */
530 
531 			    ((rand() % (BURST_SPEED * 10)) -
532 			     (BURST_SPEED * 5)) / 10.0,
533 			    -(rand() % (BURST_SPEED * 10) / 5.0),
534 
535 			    /* Starting YM (random, mostly up) */
536 
537 			    ((rand() % (BURST_SPEED * 10)) -
538 			     (BURST_SPEED * 5)) / 10.0,
539 
540 			    /* Starting ZM (random in/out) */
541 			    (rand() % 10) + 20,
542 
543 			    /* Current 'drawing color' */
544 			    color);
545 	    }
546 	}
547 
548 
549       /* Move explosions: */
550 
551       for (i = 0; i < NUM_EXPLOSIONS; i++)
552 	{
553 	  /* For each explosion that we track that is 'alive'... */
554 
555 	  if (explosions[i].alive)
556 	    {
557 	      /* Move its X,Y,Z coords based on the XM,YM,ZM values: */
558 
559 	      explosions[i].x += explosions[i].xm;
560 	      explosions[i].y += explosions[i].ym;
561 	      explosions[i].z += explosions[i].zm;
562 
563 
564 	      /* Subtly increase the YM value to simulate gravity */
565 
566 	      explosions[i].ym = explosions[i].ym + GRAVITY;
567 
568 
569 	      /* Count-down the explosion's lifespan */
570 
571 	      explosions[i].time--;
572 
573 
574 	      /* When it hits 0, kill it! */
575 
576 	      if (explosions[i].time <= 0)
577 		explosions[i].alive = 0;
578 	    }
579 	}
580 
581 
582       /* Clear bits queue: */
583 
584       num_bits = 0;
585 
586 
587       /* "Draw" explosions: */
588       /* (eg, add them to the queue) */
589 
590       for (i = 0; i < NUM_EXPLOSIONS; i++)
591 	{
592 	  /* For each explosion that we track that is 'alive'... */
593 
594 	  if (explosions[i].alive)
595 	    {
596               /* If it's not half-dead yet, always draw it...
597 	       * otherwise, draw it randomly half the time */
598 
599 	      if (explosions[i].time > 10 ||
600 		  (rand() % 10) < 5)
601 		{
602 		  /* Add this bit to the queue! */
603 
604 		  queuebit(explosions[i].x,
605 			   explosions[i].y,
606 			   explosions[i].z,
607 			   explosions[i].c);
608 		}
609 	    }
610 	}
611 
612 
613       /* Draw green borders seen on the screen: */
614 
615       for (i = -H_WIDTH; i <= H_WIDTH - 40; i = i + 40)
616 	{
617 	  /* Top and bottom far lines: */
618 
619 	  queuebit(i + (count % 40), H_HEIGHT, 40, GREEN);
620 	  queuebit(i - (count % 40) + 40, -H_HEIGHT, 40, GREEN);
621 
622 
623 	  /* Top and bottom near lines: */
624 
625 	  queuebit(i + (count % 40), -H_HEIGHT, -40, GREEN);
626 	  queuebit(i - (count % 40) + 40, H_HEIGHT, -40, GREEN);
627 	}
628 
629       for (i = -H_HEIGHT; i <= H_HEIGHT - 40; i = i + 40)
630 	{
631 	  /* Left and right far lines: */
632 
633 	  queuebit(-H_WIDTH, i + (count % 40), 40, GREEN);
634 	  queuebit(H_WIDTH, i - (count % 40) + 40, 40, GREEN);
635 
636 
637 	  /* Left and right near lines: */
638 
639 	  queuebit(-H_WIDTH, i - (count % 40) + 40, -40, GREEN);
640 	  queuebit(H_WIDTH, i + (count % 40), -40, GREEN);
641 	}
642 
643       /* NOTE: The "i +/- (count % 40)" stuff uses the global counter
644        * value to position the bits in such a way that as time goes on,
645        * the bits appear to be crawling around the edges. */
646 
647 
648       /* Draw the strange multicolored "Y" shape in the center: */
649 
650       for (i = -20; i <= 0; i = i + 5)
651 	{
652 	  queuebit(0, i, 0, GREEN);
653 	  queuebit(i, -i, 0, CYAN);
654 	  queuebit(-i, -i, 0, YELLOW);
655 	}
656 
657 
658       /* Draw everything! */
659 
660       if (fade == FALSE && zoom == NOZOOM && rotate == FALSE &&
661 	  blur == FALSE && dissolve == FALSE && heat == FALSE)
662         {
663 	  /* (Not one effect is enabled, clear the screen) */
664 
665       	  SDL_FillRect(screen, /* In the window... */
666 		     NULL,   /* The entire window (dest rect NULL for short) */
667 		     SDL_MapRGB(screen->format,
668 		     0x00, 0x00, 0x00));  /* R=0, G=0, B=0 .. black */
669 	}
670       else
671         {
672 	  /* (One or more effect enabled, do them (instead of clearing) */
673 
674 
675 	  /* (Since we're accessing raw pixel data, lock the surface
676 	     if we need to) */
677 
678 	  if (SDL_MUSTLOCK(screen))
679 	    SDL_LockSurface(screen);
680 
681 
682           if (heat == TRUE)
683             {
684               /* (Heat (burn) the screen) */
685 
686               heatscreen();
687             }
688 
689 
690 	  if (fade == TRUE)
691             {
692 	      /* (Fade the screen some) */
693 
694 	      fadeout();
695 	    }
696 
697 
698 	  if (zoom == ZOOMOUT)
699 	    {
700 	      /* (Zoom the screen away) */
701 
702 	      zoomout(zoom_x - H_WIDTH, zoom_y - H_HEIGHT);
703 	    }
704 	  else if (zoom == ZOOMIN)
705 	    {
706 	      /* (Zoom the screen in) */
707 
708 	      zoomin(zoom_x - H_WIDTH, zoom_y - H_HEIGHT);
709 	    }
710 
711 
712 	  if (rotate == TRUE)
713 	    {
714 	      /* (Rotate the screen) */
715 
716 	      rotatescreen(rotation_angle);
717 	    }
718 
719 
720 	  if (blur == TRUE)
721 	    {
722 	      /* (Blur the screen) */
723 
724 	      blurscreen();
725 	    }
726 
727 
728 	  if (dissolve == TRUE)
729 	    {
730 	      /* (Dissolve the screen) */
731 
732 	      dissolvescreen();
733 	    }
734 
735 
736 	  /* NOTE: The above is _not_ an "if / else if / else if ..." block!
737 	   * Multiple effects can be enabled at once, so we need to do
738 	   * all of them! */
739 
740 
741 	  /* (Unlock the surface if it needed locking) */
742 
743 	  if (SDL_MUSTLOCK(screen))
744 	    SDL_UnlockSurface(screen);
745 	}
746 
747 
748       /* (Sort all of the bits in the queue according to distance) */
749 
750       sortbits();
751 
752 
753       /* (Draw all of the (now sorted) bits) */
754 
755       drawbits();
756 
757 
758       /* (Swap the window's "backbuffer" onto the screen... eg, make the
759        * switch from the previous frame, to the one we just drew, an
760        * 'atomic' (all-at-once) event) */
761 
762       SDL_Flip(screen);
763 
764 
765       /* Pause for even frame rate: */
766 
767       /* (What time is it now?) */
768 
769       now = SDL_GetTicks();
770 
771 
772       /* (Has enough (or more than enough) time been eaten to make this
773        * frame appear at the appropriate time (based on the chosen FPS)?) */
774 
775       if (now < then + (1000 / FPS))
776 	{
777 	  /* (If not... idle until the time has passed) */
778 
779           SDL_Delay(then + (1000 / FPS) - now);
780 	}
781     }
782   while (done == 0);
783 }
784 
785 
786 /* Set up: */
787 
setup_sdl(void)788 void setup_sdl(void)
789 {
790   int i;
791   char filename[512];
792 
793 
794   /* Init SDL's video stuff */
795 
796   if (SDL_Init(SDL_INIT_VIDEO) < 0)
797     {
798       fprintf(stderr, "Error: %s\n", SDL_GetError());
799       exit(1);
800     }
801 
802 
803   /* Open a window: */
804 
805   screen = SDL_SetVideoMode(F_WIDTH, F_HEIGHT, 16,
806 		            SDL_SWSURFACE | SDL_ANYFORMAT);
807   if (screen == NULL)
808     {
809       fprintf(stderr, "Error: %s\n", SDL_GetError());
810       exit(1);
811     }
812 
813 
814   /* Set window's title: */
815 
816   SDL_WM_SetCaption("Explosions", "Explosions");
817 
818 
819   /* Load all of the PNG image files: */
820 
821   for (i = 0; i < NUM_BITMAPS; i++)
822     {
823       /* Construct filename ("spark#.png") */
824 
825       sprintf(filename, "/usr/local/share/explosions/spark%d.png", i + 1);
826 
827 
828       /* Load image file:  (SDL_image library call; handles PNG & alpha!) */
829 
830       spark[i] = IMG_Load(filename);
831       if (spark[i] == NULL)
832 	{
833 	  fprintf(stderr, "Error: %s - %s\n", filename, SDL_GetError());
834 	  exit(1);
835 	}
836     }
837 
838 
839   /* Seed random-number generator (so that it's pretty much random each
840    * time we start this program) */
841 
842   srand(SDL_GetTicks());
843 }
844 
845 
846 /* Init: */
847 
init(void)848 void init(void)
849 {
850   int i;
851 
852 
853   /* Initialize all tracked explosions (make them dead) */
854 
855   for (i = 0; i < NUM_EXPLOSIONS; i++)
856     explosions[i].alive = 0;
857 
858 
859   /* Set angle to 'straight-on': */
860 
861   anglex = 0;
862   angley = 0;
863 
864   /* (and calculate the trig values) */
865 
866   recalctrig();
867 }
868 
869 
870 /* Add an explosion: */
871 
add_explosion(float x,float y,float z,float xm,float ym,float zm,int time,int color)872 void add_explosion(float x, float y, float z,
873 		   float xm, float ym, float zm,
874 		   int time, int color)
875 {
876   int found, i;
877 
878 
879   /* Look for an empty slot in the "explosions[]" array: */
880 
881   found = -1;
882 
883   for (i = 0; i < NUM_EXPLOSIONS && found == -1; i++)
884     {
885       if (explosions[i].alive == 0)
886 	found = i;
887     }
888 
889 
890   /* If we found one, fill it! */
891 
892   if (found != -1)
893     {
894       explosions[found].alive = 1;    /* Alive is true */
895       explosions[found].time = time;  /* Starting age */
896 
897       explosions[found].x = x;        /* X, Y, Z coords... */
898       explosions[found].y = y;
899       explosions[found].z = z;
900 
901       explosions[found].xm = xm;      /* XM, YM, ZM values... */
902       explosions[found].ym = ym;
903       explosions[found].zm = zm;
904 
905       explosions[found].c = color;    /* Color */
906     }
907 }
908 
909 
910 /* Convert 3D to 2D: */
911 
calc3d(float x,float y,float z,int * sx,int * sy,int * sz)912 void calc3d(float x, float y, float z,
913 	    int * sx, int * sy, int * sz)
914 {
915   float xx, yy, zz;
916 
917 
918   /* Taking the "x", "y" and "z" values, rotate them based on the
919    * trig. values which should have been recalculated after the
920    * "anglex" and "angley" angles-of-rotation were changed... */
921 
922   xx = (x * cos_anglex) - (z * sin_anglex);
923   zz = (x * sin_anglex) + (z * cos_anglex);
924 
925   yy = (y * cos_angley) - (zz * sin_angley);
926   zz = (y * sin_angley) + (zz * cos_angley);
927 
928 
929   /* Based on the new (rotated) "xx", "yy" and "zz" values,
930    * convert them to screen coordinates ("sx" and "sy"), with
931    * the 3D world's (0,0,0) origin centered around the center of the window */
932 
933   *sx = (xx / ((zz + DISTANCE) / ASPECT)) + H_WIDTH;
934   *sy = (yy / ((zz + DISTANCE) / ASPECT)) + H_HEIGHT;
935 
936 
937   /* Keep track of the rotated "Z" value, since we use it to determine
938    * which sprite to use (smaller ones for more distant objects, and
939    * larger sprites for closer objects), as well as use it to sort the
940    * queue of bits-to-draw based on distance (so they overlap each other
941    * realistically) */
942 
943   *sz = ((zz + (DISTANCE / 4)) * 100) / (ASPECT / 10);
944 }
945 
946 
947 /* Recalc. trig.: */
948 
recalctrig(void)949 void recalctrig(void)
950 {
951   /* Calculate some COS and SIN values based on the "anglex" and "angley"
952    * angle-of-rotation values.  They are only calculated when the
953    * angles change (rather than calculating them for EACH object!  Eek!) */
954 
955   cos_anglex = cos(M_PI * anglex / 180.0);
956   sin_anglex = sin(M_PI * anglex / 180.0);
957 
958   cos_angley = cos(M_PI * angley / 180.0);
959   sin_angley = sin(M_PI * angley / 180.0);
960 }
961 
962 
963 /* "Draw" one explosion bit: */
964 /* (Add it to the queue of things which will need to be sorted and drawn) */
965 
queuebit(float x,float y,float z,int c)966 void queuebit(float x, float y, float z, int c)
967 {
968   int tmp_x, tmp_y, tmp_z;
969 
970 
971   /* Convert the X,Y,Z coordinates to screen coordinates and
972    * that "Z" distance value */
973 
974   calc3d(x, y, z, &tmp_x, &tmp_y, &tmp_z);
975 
976 
977   /* If it's TOO close to the viewer, we won't have a large enough
978    * sprite to draw for it, so use the largest one. */
979   /* NOT THE BEST WAY OF DOING THIS, REALLY */
980 
981   if (tmp_z < 0)
982     tmp_z = 0;
983 
984 
985   /* Add this bit to the queue of things to sort-and-draw... */
986 
987   addbit(tmp_x, tmp_y, tmp_z, c);
988 }
989 
990 
991 /* Add a bit to the bit queue: */
992 
addbit(int x,int y,int z,int c)993 void addbit(int x, int y, int z, int c)
994 {
995   /* As long as we haven't already filled the queue... */
996 
997   if (num_bits < MAX_BITS)
998     {
999       /* Add a bit to the queue: */
1000 
1001       bits[num_bits].x = x;  /* Screen X */
1002       bits[num_bits].y = y;  /* Screen Y */
1003       bits[num_bits].z = z;  /* Distance value (for sprite size & sorting) */
1004       bits[num_bits].c = c;  /* "Drawing color" */
1005 
1006 
1007       /* Increase the size of the queue */
1008 
1009       num_bits++;
1010     }
1011 }
1012 
1013 
1014 /* Sort the bits by Z: */
1015 
sortbits(void)1016 void sortbits(void)
1017 {
1018   qsort(bits,        /* Thing to sort; the queue (an array of "bit_type"'s) */
1019 	num_bits,    /* How many there are; size of the queue */
1020 	sizeof(bit_type), /* Size of data structures being sorted */
1021 	compare_bits);    /* Function to call to do the actual comparisons */
1022 }
1023 
1024 
1025 /* Comparison function for sortbits()'s qsort() call: */
1026 
compare_bits(const void * b1,const void * b2)1027 int compare_bits(const void * b1, const void * b2)
1028 {
1029   /* Return a positive, negative, or zero value to qsort() depending
1030      on which of the two bits (b1 and b2) sent had a higher Z value: */
1031 
1032   return (((bit_type *) b2) -> z) - (((bit_type *) b1) -> z);
1033 }
1034 
1035 
1036 /* Draw all bits in the queue: */
1037 
drawbits(void)1038 void drawbits(void)
1039 {
1040   int i;
1041   SDL_Rect src, dest;
1042 
1043 
1044   /* For each bit in the (by now, sorted) queue... */
1045 
1046   for (i = 0; i < num_bits; i++)
1047     {
1048       /* If the distance is something we can convert into one of the
1049        * sprite images: */
1050 
1051       if (bits[i].z >= 0 && bits[i].z < 900)
1052 	{
1053 	  /* Draw the bit at it's X,Y coordinates (taking into account
1054 	   * the size and shape of the sprite itself... we want the
1055 	   * center of the sprite to appear at the calculated screen
1056 	   * coordinates (x,y) */
1057 
1058 	  dest.x = bits[i].x - (src_width[8 - (bits[i].z / 100)] / 2);
1059 	  dest.y = bits[i].y - (src_width[8 - (bits[i].z / 100)] / 2);
1060 
1061 
1062 	  /* Within the surface (the loaded PNG image file), use the
1063 	   * appropriate shape: */
1064 
1065 	  src.x = src_start[8 - (bits[i].z / 100)];
1066 	  src.y = 0;
1067 
1068 	  src.w = src_width[8 - (bits[i].z / 100)];
1069 	  src.h = src_width[8 - (bits[i].z / 100)];
1070 
1071 
1072 	  /* (The width and heights of the destination (in 'screen' surface)
1073 	   * should match those of the source (from the loaded PNG)) */
1074 
1075 	  dest.w = src.w;
1076 	  dest.h = src.h;
1077 
1078 
1079 	  /* Actually copy the sprite onto the window's surface!  WHEW!!! */
1080 
1081 	  SDL_BlitSurface(spark[bits[i].c],  /* Copy from appropriately-colored
1082 						image... */
1083 			  &src,   /* .. the appropriate sprite. */
1084 			  screen, /* Draw it into the window... */
1085 			  &dest); /* .. at the appropriate place! */
1086 	}
1087     }
1088 }
1089 
1090 
1091 /* Fade every pixel in the window (in the "screen" surface) */
1092 /* This func. is called instead of the screen-clearing SDL_FillRect */
1093 /* if "zoom" is set to 'TRUE'... */
1094 
fadeout(void)1095 void fadeout(void)
1096 {
1097   int x, y;
1098   Uint8 r, g, b;
1099   Uint32 nr, ng, nb;
1100 
1101 
1102   /* Traverse the entire window: */
1103 
1104   for (y = 0; y < F_HEIGHT; y++)
1105     {
1106       for (x = 0; x < F_WIDTH; x++)
1107 	{
1108 	  /* Get the RGB values of the pixel: */
1109 
1110 	  SDL_GetRGB(getpixel(screen, x, y), screen->format, &r, &g, &b);
1111 
1112 
1113 	  /* Darken it some */
1114 
1115 	  nr = (r * 3) >> 2;  /* == (r * 3) / 4 == r * 0.75 */
1116 	  ng = (g * 3) >> 2;
1117 	  nb = (b * 3) >> 2;
1118 
1119 
1120 	  /* Draw the new pixel: */
1121 
1122 	  putpixel(screen, x, y,
1123 	           SDL_MapRGB(screen->format,
1124 		              (Uint8) nr,
1125 			      (Uint8) ng,
1126 			      (Uint8) nb));
1127 	}
1128     }
1129 }
1130 
1131 
1132 /* Zoom the entire image in the window ("screen surface") out some */
1133 /* This func. is called instead of the screen-clearing SDL_FillRect */
1134 /* if "zoom" is set to 'ZOOMOUT'... */
1135 
zoomout(int zoom_x,int zoom_y)1136 void zoomout(int zoom_x, int zoom_y)
1137 {
1138   int x, y;
1139   SDL_Surface * tmp;
1140 
1141 
1142   /* Create a temp. surface to draw onto: */
1143 
1144   tmp = SDL_CreateRGBSurface(SDL_SWSURFACE, F_WIDTH, F_HEIGHT, screen->format->BitsPerPixel,
1145 			     0, 0, 0, 0);
1146 
1147   if (tmp == NULL)
1148     {
1149       /* Couldn't create it!? */
1150 
1151       printf("%s\n", SDL_GetError());
1152       exit(1);
1153     }
1154 
1155 
1156   /* Lock temp. surface if we need to (accessing raw pixels) */
1157 
1158   if (SDL_MUSTLOCK(tmp))
1159     SDL_LockSurface(tmp);
1160 
1161 
1162   /* For every pixel in the window... */
1163 
1164   for (y = 0; y < F_HEIGHT; y++)
1165     {
1166       for (x = 0; x < F_WIDTH; x++)
1167 	{
1168 	  /* Draw a smaller version of the existing image (screen) into
1169 	     the temp. surface: */
1170 
1171 	  putpixel(tmp,          /* Drawing into temp. surface */
1172 		   ((((x - H_WIDTH) * 3) / 4) + /* X scaled by .75*/
1173 		    H_WIDTH +                   /* (re-centered) */
1174 		    zoom_x),                    /* and moved (zoom_x offset) */
1175 		   ((((y - H_HEIGHT) * 3) / 4) +
1176 		    H_HEIGHT +                  /* (ditto for Y) */
1177 		    zoom_y),
1178 		   getpixel(screen, x, y));  /* A pixel from "screen" */
1179 	}
1180     }
1181 
1182 
1183   /* Unlock temp. surface: */
1184 
1185   if (SDL_MUSTLOCK(tmp))
1186     SDL_UnlockSurface(tmp);
1187 
1188 
1189   /* Copy entire temp. surface into window: */
1190 
1191   SDL_BlitSurface(tmp, NULL, screen, NULL);
1192 
1193 
1194   /* Free the temp. surface from memory, now that we're done with it: */
1195 
1196   SDL_FreeSurface(tmp);
1197 }
1198 
1199 
1200 /* Zoom the entire image in the window ("screen surface") in some */
1201 /* This func. is called instead of the screen-clearing SDL_FillRect */
1202 /* if "zoom" has been set to 'ZOOMIN'... */
1203 
zoomin(int zoom_x,int zoom_y)1204 void zoomin(int zoom_x, int zoom_y)
1205 {
1206   int x, y;
1207   Uint32 pix;
1208   SDL_Surface * tmp;
1209   int nx, ny;
1210 
1211 
1212   /* Create a temp. surface to draw onto: */
1213 
1214   tmp = SDL_CreateRGBSurface(SDL_SWSURFACE, F_WIDTH, F_HEIGHT,
1215 		  	     screen->format->BitsPerPixel,
1216 			     0, 0, 0, 0);
1217 
1218   if (tmp == NULL)
1219     {
1220       /* Couldn't create it!? */
1221 
1222       printf("%s\n", SDL_GetError());
1223       exit(1);
1224     }
1225 
1226 
1227   /* Lock temp. surface if we need to (accessing raw pixels) */
1228 
1229   if (SDL_MUSTLOCK(tmp))
1230     SDL_LockSurface(tmp);
1231 
1232 
1233   /* For every pixel in the window... */
1234 
1235   for (y = 0; y < F_HEIGHT; y++)
1236     {
1237       for (x = 0; x < F_WIDTH; x++)
1238 	{
1239 	  /* Get the pixel's color: */
1240 
1241 	  pix = getpixel(screen, x, y);
1242 
1243 
1244 	  /* Calculate new X/Y location: */
1245 
1246 	  nx = ((((x - H_WIDTH) * 4) / 3) +  /* X scaled up by 125% */
1247 		H_WIDTH +                    /* (re-centered) */
1248 		zoom_x);                     /* and moved (zoom_x offset) */
1249 
1250 	  ny = ((((y - H_HEIGHT) * 4) / 3) +
1251 		H_HEIGHT +                   /* (Ditto for Y) */
1252 		zoom_y);
1253 
1254 
1255 	  /* Draw the pixel (2x2 to fill in gaps) into temp. surface */
1256 
1257 	  putpixel(tmp, nx + 0, ny + 0, pix);
1258 	  putpixel(tmp, nx + 1, ny + 0, pix);
1259 	  putpixel(tmp, nx + 0, ny + 1, pix);
1260 	  putpixel(tmp, nx + 1, ny + 1, pix);
1261 	}
1262     }
1263 
1264 
1265   /* Unlock temp. surface: */
1266 
1267   if (SDL_MUSTLOCK(tmp))
1268     SDL_UnlockSurface(tmp);
1269 
1270 
1271   /* Copy entire temp. surface into window: */
1272 
1273   SDL_BlitSurface(tmp, NULL, screen, NULL);
1274 
1275 
1276   /* Free the temp. surface from memory, now that we're done with it: */
1277 
1278   SDL_FreeSurface(tmp);
1279 }
1280 
1281 
1282 /* Rotate the entire image in the window ("screen surface") some angle */
1283 /* This func. is called instead of the screen-clearing SDL_FillRect */
1284 /* if "rotate" has been set to 'TRUE'... */
1285 
rotatescreen(int angle)1286 void rotatescreen(int angle)
1287 {
1288   int x, y;
1289   Uint32 pix;
1290   SDL_Surface * tmp;
1291   int nx, ny;
1292   float rad, cos_r, sin_r;
1293 
1294 
1295   /* Create a temp. surface to draw onto: */
1296 
1297   tmp = SDL_CreateRGBSurface(SDL_SWSURFACE, F_WIDTH, F_HEIGHT,
1298 		             screen->format->BitsPerPixel,
1299 			     0, 0, 0, 0);
1300 
1301   if (tmp == NULL)
1302     {
1303       /* Couldn't create it!? */
1304 
1305       printf("%s\n", SDL_GetError());
1306       exit(1);
1307     }
1308 
1309 
1310   /* Lock temp. surface if we need to (accessing raw pixels) */
1311 
1312   if (SDL_MUSTLOCK(tmp))
1313     SDL_LockSurface(tmp);
1314 
1315 
1316   /* Convert rotation angle into radians: */
1317 
1318   rad = - (3.14159 * angle) / 180;
1319 
1320 
1321   /* Get COSINE and SINE of the angle, for use later
1322      (rather than calculating the same value over and over!) */
1323 
1324   cos_r = cos(rad);
1325   sin_r = sin(rad);
1326 
1327 
1328   /* For every pixel on the screen: */
1329 
1330   for (y = 0; y < F_HEIGHT; y++)
1331     {
1332       for (x = 0; x < F_WIDTH; x++)
1333         {
1334 	  /* Get the pixel's color: */
1335 
1336 	  pix = getpixel(screen, x, y);
1337 
1338 
1339 	  /* Calculate new X/Y location: */
1340 
1341 	  nx = (((x - H_WIDTH) * cos_r) -   /* Rotated around... */
1342 		((y - H_HEIGHT) * sin_r) +  /* ...old pixel's position */
1343 		H_WIDTH);                   /* (Re-centered) */
1344 
1345 	  ny = (((x - H_WIDTH) * sin_r) +
1346 		((y - H_HEIGHT) * cos_r) +  /* (Similar for Y) */
1347 		H_HEIGHT);
1348 
1349 
1350 	  /* Draw the pixel (2x2 to fill in gaps) into temp. surface */
1351 
1352 	  putpixel(tmp, nx + 0, ny + 0, pix);
1353 	  putpixel(tmp, nx + 1, ny + 0, pix);
1354 	  putpixel(tmp, nx + 0, ny + 1, pix);
1355 	  putpixel(tmp, nx + 1, ny + 1, pix);
1356 	}
1357     }
1358 
1359 
1360   /* Unlock temp. surface: */
1361 
1362   if (SDL_MUSTLOCK(tmp))
1363     SDL_UnlockSurface(tmp);
1364 
1365 
1366   /* Copy entire temp. surface into window: */
1367 
1368   SDL_BlitSurface(tmp, NULL, screen, NULL);
1369 
1370 
1371   /* Free the temp. surface from memory, now that we're done with it: */
1372 
1373   SDL_FreeSurface(tmp);
1374 }
1375 
1376 
1377 /* Blur the entire image in the window ("screen surface") */
1378 /* This func. is called instead of the screen-clearing SDL_FillRect */
1379 /* if "blur" has been set to 'TRUE'... */
1380 
blurscreen(void)1381 void blurscreen(void)
1382 {
1383   int x, y;
1384   int xx, yy;
1385   Uint8 r, g, b;
1386   Uint32 nr, ng, nb;   /* Bigger (Uint32) because we total many RGBs */
1387   SDL_Surface * tmp;
1388 
1389 
1390   /* Create a temp. surface to draw onto: */
1391 
1392   tmp = SDL_CreateRGBSurface(SDL_SWSURFACE, F_WIDTH, F_HEIGHT,
1393 		             screen->format->BitsPerPixel,
1394 			     0, 0, 0, 0);
1395 
1396   if (tmp == NULL)
1397     {
1398       /* Couldn't create it!? */
1399 
1400       printf("%s\n", SDL_GetError());
1401       exit(1);
1402     }
1403 
1404 
1405   /* Lock temp. surface if we need to (accessing raw pixels) */
1406 
1407   if (SDL_MUSTLOCK(tmp))
1408     SDL_LockSurface(tmp);
1409 
1410 
1411   /* For every pixel (excluding outer edges)... */
1412 
1413   for (y = 1; y < F_HEIGHT - 1; y += 2)
1414     {
1415       for (x = 1; x < F_WIDTH - 1; x += 2)
1416         {
1417 	  /* Clear RGB color totals: */
1418 
1419 	  nr = 0;
1420 	  ng = 0;
1421 	  nb = 0;
1422 
1423 
1424 	  /* For the 9 pixels surrounding this location... */
1425 
1426 	  for (yy = -1; yy <= 1; yy++)
1427 	    {
1428 	      for (xx = -1; xx <= 1; xx++)
1429 		{
1430 		  /* Get the RGB values for the pixel: */
1431 
1432 		  SDL_GetRGB(getpixel(screen, x + xx, y + yy),
1433 			     screen->format, &r, &g, &b);
1434 
1435 
1436 		  /* Increase the RGB color totals: */
1437 
1438 		  nr = nr + r;
1439 		  ng = ng + g;
1440 		  nb = nb + b;
1441 		}
1442 	    }
1443 
1444 
1445 	  /* Calculate the average (total divided by how many) */
1446 
1447 	  nr = nr >> 4;
1448 	  ng = ng >> 4;
1449 	  nb = nb >> 4;
1450 
1451 
1452 	  /* Draw the averaged pixel onto the temp. surface: */
1453 
1454 	  for (yy = y; yy < y + 2; yy++)
1455 	  {
1456 	    for (xx = x; xx < x + 2; xx++)
1457 	    {
1458 	      putpixel(tmp, xx, yy,
1459 	               SDL_MapRGB(screen->format,
1460 		                  (Uint8) nr,
1461 			          (Uint8) ng,
1462 			          (Uint8) nb));
1463 	    }
1464 	  }
1465 	}
1466     }
1467 
1468 
1469   /* Unlock temp. surface: */
1470 
1471   if (SDL_MUSTLOCK(tmp))
1472     SDL_UnlockSurface(tmp);
1473 
1474 
1475   /* Copy entire temp. surface into window: */
1476 
1477   SDL_BlitSurface(tmp, NULL, screen, NULL);
1478 
1479 
1480   /* Free the temp. surface from memory, now that we're done with it: */
1481 
1482   SDL_FreeSurface(tmp);
1483 }
1484 
1485 
1486 /* Dissolve the entire image in the window ("screen surface") some angle */
1487 /* This func. is called instead of the screen-clearing SDL_FillRect */
1488 /* if "dissolve" has been set to 'TRUE'... */
1489 
dissolvescreen(int angle)1490 void dissolvescreen(int angle)
1491 {
1492   int x, y;
1493 
1494 
1495   /* For every pixel... */
1496 
1497   for (y = 0; y < F_HEIGHT; y++)
1498     {
1499       for (x = 0; x < F_WIDTH; x++)
1500         {
1501 	  /* Based on random chance (pick a number between 0 and 7...
1502 	     is it less than 3?...) */
1503 
1504 	  if ((rand() % 8) < 3)
1505 	    {
1506 	      /* (...if so) Set the pixel to black: */
1507 
1508 	      putpixel(screen, x, y,
1509 		       SDL_MapRGB(screen->format, 0x00, 0x00, 0x00));
1510 	    }
1511 	}
1512     }
1513 }
1514 
1515 
1516 /* Heat (blur with fire) the entire image in the window */
1517 /* ("screen surface") some angle */
1518 /* This func. is called instead of the screen-clearing SDL_FillRect */
1519 /* if "heat" has been set to 'TRUE'... */
1520 
heatscreen(int angle)1521 void heatscreen(int angle)
1522 {
1523   int x, y;
1524   int xx, yy, xoff;
1525   Uint8 r, g, b;
1526   Uint32 nr, ng, nb;
1527   SDL_Surface * tmp;
1528 
1529 
1530   /* Create a temp. surface to draw onto: */
1531 
1532   tmp = SDL_CreateRGBSurface(SDL_SWSURFACE, F_WIDTH, F_HEIGHT,
1533 		  	     screen->format->BitsPerPixel,
1534 			     0, 0, 0, 0);
1535 
1536   if (tmp == NULL)
1537     {
1538       /* Couldn't create it!? */
1539 
1540       printf("%s\n", SDL_GetError());
1541       exit(1);
1542     }
1543 
1544 
1545   /* Lock temp. surface if we need to (accessing raw pixels) */
1546 
1547   if (SDL_MUSTLOCK(tmp))
1548     SDL_LockSurface(tmp);
1549 
1550 
1551   /* For every pixel except the outer edges: */
1552 
1553   for (y = 1; y < F_HEIGHT - 1; y++)
1554     {
1555       /* Pick a random offset for this row (for 'wavering' effect of heat) */
1556 
1557       xoff = (rand() % 3) - 1;
1558 
1559       for (x = 1; x < F_WIDTH - 1; x++)
1560         {
1561 	  /* Clear RGB totals: */
1562 
1563 	  nr = 0;
1564 	  ng = 0;
1565 	  nb = 0;
1566 
1567 
1568 	  /* For the lower-surrounding locations of this pixel: */
1569 
1570 	  for (yy = 0; yy <= 1; yy++)
1571 	    {
1572 	      for (xx = xoff - 1; xx <= xoff + 1; xx++)
1573 		{
1574 		  /* Get the RGB values for the pixel: */
1575 
1576 		  SDL_GetRGB(getpixel(screen, x + xx, y + yy),
1577 			     screen->format, &r, &g, &b);
1578 
1579 
1580 		  /* Set the new colors: */
1581 
1582 		  nr += (r +              /* Red gets increased a little */
1583 			 ((g + b) / 4));  /* more by the current B & G */
1584 		  ng += g;
1585 		  nb += b;
1586 		}
1587 	    }
1588 
1589 
1590 	  /* "Average" the totals: */
1591 
1592 	  nr = nr / 6;    /* Red is true average of total */
1593 	  ng = ng / 7;    /* Green is slightly smaller */
1594 	  nb = nb >> 3;   /* Blue is even smaller */
1595 
1596 	  /* NOTE: The above causes the color to have much more red,
1597 	     and slightly more green (therefore orange/yellow) than
1598 	     if all values were simply truly averaged (divided by 6 each) */
1599 
1600 
1601 	  /* If the red is greater than max possible value,
1602 	     cut it off (so it doesn't wrap around, causing weird
1603 	     effects... brighter-than-bright would become dark!!!) */
1604 
1605 	  if (nr >= 0xFF)
1606 	    nr = 0xFF;
1607 
1608 
1609 	  /* Draw the new, more red pixel onto the temp. surface: */
1610 
1611 	  putpixel(tmp, x, y,
1612 	           SDL_MapRGB(screen->format,
1613 		              (Uint8) nr,
1614 			      (Uint8) ng,
1615 			      (Uint8) nb));
1616 	}
1617     }
1618 
1619 
1620   /* Unlock temp. surface: */
1621 
1622   if (SDL_MUSTLOCK(tmp))
1623     SDL_UnlockSurface(tmp);
1624 
1625 
1626   /* Copy entire temp. surface into window: */
1627 
1628   SDL_BlitSurface(tmp, NULL, screen, NULL);
1629 
1630 
1631   /* Free the temp. surface from memory, now that we're done with it: */
1632 
1633   SDL_FreeSurface(tmp);
1634 }
1635 
1636 
1637 /* Get a single pixel from a surface (to convert to R, G, B) */
1638 
getpixel(SDL_Surface * surface,int x,int y)1639 Uint32 getpixel(SDL_Surface * surface, int x, int y)
1640 {
1641   int bpp;
1642   Uint8 * p;
1643 
1644 
1645   /* Determine bytes-per-pixel for the surface in question: */
1646 
1647   bpp = surface->format->BytesPerPixel;
1648 
1649 
1650   /* Set a pointer to the exact location in memory of the pixel
1651      in question: */
1652 
1653   p = (Uint8 *) (surface->pixels +       /* Start at beginning of RAM */
1654 		 (y * surface->pitch) +  /* Go down Y lines */
1655 		 (x * bpp));             /* Go in X pixels */
1656 
1657 
1658   /* Assuming the X/Y values are within the bounds of this surface... */
1659 
1660   if (x >= 0 && y >= 0 && x < surface -> w && y < surface -> h)
1661     {
1662       /* Return the correctly-sized piece of data containing the
1663 	 pixel's value (an 8-bit palette value, or a 16-, 24- or 32-bit
1664 	 RGB value) */
1665 
1666       if (bpp == 1)         /* 8-bit display */
1667 	return *p;
1668       else if (bpp == 2)    /* 16-bit display */
1669 	return *(Uint16 *)p;
1670       else if (bpp == 3)    /* 24-bit display */
1671 	{
1672 	  /* Depending on the byte-order, it could be stored RGB or BGR! */
1673 
1674 	  if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
1675 	    return p[0] << 16 | p[1] << 8 | p[2];
1676 	  else
1677 	    return p[0] | p[1] << 8 | p[2] << 16;
1678 	}
1679       else if (bpp == 4)    /* 32-bit display */
1680 	return *(Uint32 *)p;
1681       else
1682 	return 0;           /* (Should never occur) */
1683     }
1684   else
1685     return 0;               /* (Out of bounds?  Just return zero) */
1686 }
1687 
1688 
1689 /* Draw a single pixel into the surface: */
1690 
putpixel(SDL_Surface * surface,int x,int y,Uint32 pixel)1691 void putpixel(SDL_Surface * surface, int x, int y, Uint32 pixel)
1692 {
1693   int bpp;
1694   Uint8 * p;
1695 
1696   /* Determine bytes-per-pixel for the surface in question: */
1697 
1698   bpp = surface->format->BytesPerPixel;
1699 
1700 
1701   /* Set a pointer to the exact location in memory of the pixel
1702      in question: */
1703 
1704   p = (Uint8 *) (surface->pixels +       /* Start at beginning of RAM */
1705 		 (y * surface->pitch) +  /* Go down Y lines */
1706 		 (x * bpp));             /* Go in X pixels */
1707 
1708 
1709   /* Assuming the X/Y values are within the bounds of this surface... */
1710 
1711   if (x >= 0 && y >= 0 && x < surface -> w && y < surface -> h)
1712     {
1713       /* Set the (correctly-sized) piece of data in the surface's RAM
1714 	 to the pixel value sent in: */
1715 
1716       if (bpp == 1)
1717 	*p = pixel;
1718       else if (bpp == 2)
1719 	*(Uint16 *)p = pixel;
1720       else if (bpp == 3)
1721 	{
1722 	  if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
1723 	    {
1724 	      p[0] = (pixel >> 16) & 0xff;
1725 	      p[1] = (pixel >> 8) & 0xff;
1726 	      p[2] = pixel & 0xff;
1727 	    }
1728 	  else
1729 	    {
1730 	      p[0] = pixel & 0xff;
1731 	      p[1] = (pixel >> 8) & 0xff;
1732 	      p[2] = (pixel >> 16) & 0xff;
1733 	    }
1734 	}
1735       else if (bpp == 4)
1736 	{
1737 	  *(Uint32 *)p = pixel;
1738 	}
1739     }
1740 }
1741