2084cbf27bc77cfd150c57f5d44f2c806e4cbab1
[xscreensaver] / hacks / eruption.c
1 /* Eruption, Copyright (c) 2002-2003 W.P. van Paassen <peter@paassen.tmfweb.nl>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Module - "eruption.c"
12  *
13  * [02-2003] - W.P. van Paassen: Improvements, added some code of jwz from the pyro hack for a spherical distribution of the particles
14  * [01-2003] - W.P. van Paassen: Port to X for use with XScreenSaver, the shadebob hack by Shane Smit was used as a template
15  * [04-2002] - W.P. van Paassen: Creation for the Demo Effects Collection (http://demo-effects.sourceforge.net)
16  */
17
18 #include <math.h>
19 #include "screenhack.h"
20
21 /*#define VERBOSE*/ 
22
23 /* Slightly whacked, for better explosions
24  */
25 #define PI_2000 6284
26 #define SPREAD 15
27
28 /*particle structure*/
29 typedef struct 
30 {
31   short xpos, ypos, xdir, ydir;
32   unsigned char colorindex;
33   unsigned char dead;
34 } PARTICLE;
35
36 struct state {
37   Display *dpy;
38   Window window;
39
40  int sin_cache[PI_2000];
41  int cos_cache[PI_2000];
42
43   PARTICLE *particles;
44   unsigned short iWinWidth, iWinHeight;
45   unsigned char **fire;
46   unsigned short nParticleCount;
47   unsigned char xdelta, ydelta, decay;
48   signed char gravity;
49   signed short heat;
50
51   int cycles, delay;
52   GC gc;
53   signed short iColorCount;
54   unsigned long *aiColorVals;
55   XImage *pImage;
56
57   int draw_i;
58 };
59
60 static void
61 cache(struct state *st) /* jwz */
62 {               /*needs to be run once. Could easily be */
63   int i;        /*reimplemented to run and cache at compile-time,*/
64   double dA;    
65   for (i=0; i<PI_2000; i++)
66     {
67       dA=sin(((double) (random() % (PI_2000/2)))/1000.0);
68       /*Emulation of spherical distribution*/
69       dA+=asin(frand(1.0))/M_PI_2*0.1;
70       /*Approximating the integration of the binominal, for
71         well-distributed randomness*/
72       st->cos_cache[i]=-abs((int) (cos(((double)i)/1000.0)*dA*st->ydelta));
73       st->sin_cache[i]=(int) (sin(((double)i)/1000.0)*dA*st->xdelta);
74     }
75 }
76
77 static void init_particle(struct state *st, PARTICLE* particle, unsigned short xcenter, unsigned short ycenter)
78 {
79   int v = random() % PI_2000;
80   particle->xpos = xcenter - SPREAD + (random() % (SPREAD * 2));
81   particle->ypos = ycenter - SPREAD + (random() % (SPREAD * 2));;
82   particle->xdir = st->sin_cache[v];
83   particle->ydir = st->cos_cache[v];
84   particle->colorindex = st->iColorCount-1;
85   particle->dead = 0;
86 }
87
88 static void Execute( struct state *st )
89 {
90   int i, j;
91   unsigned int temp;
92
93   /* move and draw particles into st->fire array */
94   
95   for (i = 0; i < st->nParticleCount; i++)
96     {
97       if (!st->particles[i].dead)
98         {
99           st->particles[i].xpos += st->particles[i].xdir;
100           st->particles[i].ypos += st->particles[i].ydir;
101           
102           /* is particle dead? */
103           
104           if (st->particles[i].colorindex == 0)
105             {
106               st->particles[i].dead = 1;
107               continue;
108             }
109           
110           if (st->particles[i].xpos < 1)
111             {
112               st->particles[i].xpos = 1;
113               st->particles[i].xdir = -st->particles[i].xdir - 4;
114               st->particles[i].colorindex = st->iColorCount;
115             }
116           else if (st->particles[i].xpos >= st->iWinWidth - 2)
117             {
118               st->particles[i].xpos = st->iWinWidth - 2;
119               st->particles[i].xdir = -st->particles[i].xdir + 4;
120               st->particles[i].colorindex = st->iColorCount;
121             }
122           
123           if (st->particles[i].ypos < 1)
124             {
125               st->particles[i].ypos = 1;
126               st->particles[i].ydir = -st->particles[i].ydir;
127               st->particles[i].colorindex = st->iColorCount;
128             }
129           else if (st->particles[i].ypos >= st->iWinHeight - 3)
130             {
131               st->particles[i].ypos = st->iWinHeight- 3;
132               st->particles[i].ydir = (-st->particles[i].ydir >> 2) - (random() % 2);
133               st->particles[i].colorindex = st->iColorCount;
134             }
135           
136           /* st->gravity kicks in */
137           st->particles[i].ydir += st->gravity;
138           
139           /* particle cools off */
140           st->particles[i].colorindex--;
141           
142           /* draw particle */
143           st->fire[st->particles[i].ypos][st->particles[i].xpos] = st->particles[i].colorindex;
144           st->fire[st->particles[i].ypos][st->particles[i].xpos - 1] = st->particles[i].colorindex;
145           st->fire[st->particles[i].ypos + 1][st->particles[i].xpos] = st->particles[i].colorindex;
146           st->fire[st->particles[i].ypos - 1][st->particles[i].xpos] = st->particles[i].colorindex;
147           st->fire[st->particles[i].ypos][st->particles[i].xpos + 1] = st->particles[i].colorindex;
148         }
149     }
150   
151   /* create st->fire effect */ 
152   for (i = 0; i < st->iWinHeight; i++)
153     {
154       for (j = 0; j < st->iWinWidth; j++)
155         {
156           if (j + 1 >= st->iWinWidth)
157             temp = 0;
158           else
159             temp = st->fire[i][j + 1];
160
161           if (j - 1 >= 0)  
162             temp += st->fire[i][j - 1];
163
164           if (i - 1 >= 0)
165             {
166               temp += st->fire[i - 1][j];
167               if (j - 1 >= 0)
168                 temp += st->fire[i - 1][j - 1];
169               if (j + 1 < st->iWinWidth)
170                 temp += st->fire[i - 1][j + 1];
171             }
172           
173           if (i + 1 < st->iWinHeight)
174             {
175               temp += st->fire[i + 1][j];
176               if (j + 1 < st->iWinWidth)
177                 temp += st->fire[i + 1][j + 1];
178               if (j - 1 >= 0)
179                 temp += st->fire[i + 1][j - 1];
180             }
181           
182           temp >>= 3;
183           
184           if (temp > st->decay)
185             {
186               temp -= st->decay;
187             }
188           else
189             temp = 0;
190           
191           st->fire[i][j] = temp;
192         }
193     }
194   
195   memset( st->pImage->data, 0, st->pImage->bytes_per_line * st->pImage->height );
196   
197   /* draw st->fire array to screen */
198   for (i = 0; i < st->iWinHeight; ++i)
199     {
200       for (j = 0; j < st->iWinWidth; ++j)
201         {
202           if (st->fire[i][j] > 0)
203             XPutPixel( st->pImage, j, i, st->aiColorVals[ st->fire[i][j] ] );
204         }
205     }
206   XPutImage( st->dpy, st->window, st->gc, st->pImage,
207              0,0,0,0, st->iWinWidth, st->iWinHeight );
208 }
209
210 static unsigned long * SetPalette(struct state *st)
211 {
212         XWindowAttributes XWinAttribs;
213         XColor Color, *aColors;
214         signed short iColor;
215
216         XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
217         
218         st->iColorCount = get_integer_resource(st->dpy,  "ncolors", "Integer" );
219         if( st->iColorCount < 16 )      st->iColorCount = 16;
220         if( st->iColorCount > 255 )     st->iColorCount = 256;
221
222         aColors    = calloc( st->iColorCount, sizeof(XColor) );
223         st->aiColorVals = calloc( st->iColorCount, sizeof(unsigned long) );
224         
225         Color.red = Color.green = Color.blue = 65535 / st->iColorCount;
226
227         /* create st->fire palette */
228         for( iColor=0; iColor < st->iColorCount; iColor++ )
229         {
230           if (iColor < st->iColorCount >> 3)
231             {
232               /* black to blue */
233               aColors[iColor].red = 0;
234               aColors[iColor].green = 0;
235               aColors[iColor].blue = Color.blue * (iColor << 1);
236             }
237           else if (iColor < st->iColorCount >> 2)
238             {
239               /* blue to red */
240               signed short temp = (iColor - (st->iColorCount >> 3));
241               aColors[iColor].red = Color.red * (temp << 3);
242               aColors[iColor].green = 0;
243               aColors[iColor].blue = 16383 - Color.blue * (temp << 1);
244             }
245           else if (iColor < (st->iColorCount >> 2) + (st->iColorCount >> 3))
246             {
247               /* red to yellow */
248               signed short temp = (iColor - (st->iColorCount >> 2)) << 3;
249               aColors[iColor].red = 65535;
250               aColors[iColor].green = Color.green * temp;
251               aColors[iColor].blue = 0;
252             }
253           else if (iColor < st->iColorCount >> 1)
254             {
255               /* yellow to white */
256               signed int temp = (iColor - ((st->iColorCount >> 2) + (st->iColorCount >> 3))) << 3;
257               aColors[iColor].red = 65535;
258               aColors[iColor].green = 65535;
259               aColors[iColor].blue = Color.blue * temp;
260             }
261           else
262             {
263               /* white */
264               aColors[iColor].red = aColors[iColor].green = aColors[iColor].blue = 65535;
265             }
266
267           if( !XAllocColor( st->dpy, XWinAttribs.colormap, &aColors[ iColor ] ) )
268             {
269               /* start all over with less colors */     
270               XFreeColors( st->dpy, XWinAttribs.colormap, st->aiColorVals, iColor, 0 );
271               free( aColors );
272               free( st->aiColorVals );
273               (st->iColorCount)--;
274               aColors     = calloc( st->iColorCount, sizeof(XColor) );
275               st->aiColorVals = calloc( st->iColorCount, sizeof(unsigned long) );
276               iColor = -1;
277             }
278           else
279             st->aiColorVals[ iColor ] = aColors[ iColor ].pixel;
280         }
281
282         if (st->heat < st->iColorCount)
283           st->iColorCount = st->heat;
284         
285         free( aColors );
286
287         XSetWindowBackground( st->dpy, st->window, st->aiColorVals[ 0 ] );
288
289         return st->aiColorVals;
290 }
291
292
293 static void Initialize( struct state *st )
294 {
295         XGCValues gcValues;
296         XWindowAttributes XWinAttribs;
297         int iBitsPerPixel, i;
298
299         /* Create the Image for drawing */
300         XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
301
302         /* Find the preferred bits-per-pixel. (jwz) */
303         {
304                 int pfvc = 0;
305                 XPixmapFormatValues *pfv = XListPixmapFormats( st->dpy, &pfvc );
306                 for( i=0; i<pfvc; i++ )
307                         if( pfv[ i ].depth == XWinAttribs.depth )
308                         {
309                                 iBitsPerPixel = pfv[ i ].bits_per_pixel;
310                                 break;
311                         }
312                 if( pfv )
313                         XFree (pfv);
314         }
315
316         /*  Create the GC. */
317         st->gc = XCreateGC( st->dpy, st->window, 0, &gcValues );
318
319         st->pImage = XCreateImage( st->dpy, XWinAttribs.visual, XWinAttribs.depth, ZPixmap, 0, NULL,
320                                                           XWinAttribs.width, XWinAttribs.height, BitmapPad( st->dpy ), 0 );
321         (st->pImage)->data = calloc((st->pImage)->bytes_per_line, (st->pImage)->height);
322
323         st->iWinWidth = XWinAttribs.width;
324         st->iWinHeight = XWinAttribs.height;
325
326         /* create st->fire array */
327         st->fire = calloc( st->iWinHeight, sizeof(unsigned char*));
328         for (i = 0; i < st->iWinHeight; ++i)
329           st->fire[i] = calloc( st->iWinWidth, sizeof(unsigned char));
330
331         /*create st->particles */
332         st->particles = malloc (st->nParticleCount * sizeof(PARTICLE));
333 }
334
335 static void *
336 eruption_init (Display *dpy, Window window)
337 {
338   struct state *st = (struct state *) calloc (1, sizeof(*st));
339         XWindowAttributes XWinAttribs;
340         unsigned short sum = 0;
341 #ifdef VERBOSE
342         time_t nTime = time( NULL );
343         unsigned short iFrame = 0;
344 #endif  /*  VERBOSE */
345         int i;
346
347   st->dpy = dpy;
348   st->window = window;
349
350         st->nParticleCount = get_integer_resource(st->dpy,  "particles", "Integer" );
351         if (st->nParticleCount < 100)
352           st->nParticleCount = 100;
353         if (st->nParticleCount > 2000)
354           st->nParticleCount = 2000;
355
356         st->decay = get_integer_resource(st->dpy,  "cooloff", "Integer" );
357         if (st->decay <= 0)
358           st->decay = 0;
359         if (st->decay > 10)
360           st->decay = 10;
361
362         st->gravity = get_integer_resource(st->dpy,  "gravity", "Integer" );
363         if (st->gravity < -5)
364           st->gravity = -5;
365         if (st->gravity > 5)
366           st->gravity = 5;
367
368         st->heat = get_integer_resource(st->dpy,  "heat", "Integer" );
369         if (st->heat < 64)
370           st->heat = 64;
371         if (st->heat > 256)
372           st->heat = 256;
373
374 #ifdef VERBOSE 
375         printf( "%s: Allocated %d st->particles\n", progclass, st->nParticleCount );
376 #endif  /*  VERBOSE */
377
378         Initialize( st );
379
380         st->ydelta = 0;
381         while (sum < (st->iWinHeight >> 1) - SPREAD)
382           {
383             st->ydelta++;
384             sum += st->ydelta;
385           }
386
387         sum = 0;
388         while (sum < (st->iWinWidth >> 3))
389           {
390             st->xdelta++;
391             sum += st->xdelta;
392           }
393
394         st->delay = get_integer_resource(st->dpy,  "delay", "Integer" );
395         st->cycles = get_integer_resource(st->dpy,  "cycles", "Integer" );
396         i = st->cycles;
397
398         cache(st);
399         
400         XGetWindowAttributes( st->dpy, st->window, &XWinAttribs );
401         XFreeColors( st->dpy, XWinAttribs.colormap, st->aiColorVals, st->iColorCount, 0 );
402         free( st->aiColorVals );
403         st->aiColorVals = SetPalette( st );
404         XClearWindow( st->dpy, st->window );
405         memset( st->pImage->data, 0, st->pImage->bytes_per_line * st->pImage->height );
406
407         st->draw_i = -1;
408
409         return st;
410 }
411
412
413 static unsigned long
414 eruption_draw (Display *dpy, Window window, void *closure)
415 {
416   struct state *st = (struct state *) closure;
417             
418   if( st->draw_i < 0 || st->draw_i++ >= st->cycles )
419     {
420       /* compute random center */
421       unsigned short xcenter, ycenter;
422       xcenter = random() % st->iWinWidth;
423       ycenter = random() % st->iWinHeight;
424                 
425       for (st->draw_i = 0; st->draw_i < st->nParticleCount; st->draw_i++)
426         init_particle(st, st->particles + st->draw_i, xcenter, ycenter);
427       st->draw_i = 0;
428     }
429             
430   Execute( st );
431             
432 #ifdef VERBOSE
433   iFrame++;
434   if( nTime - time( NULL ) )
435     {
436       printf( "%s: %d FPS\n", progclass, iFrame );
437       nTime = time( NULL );
438       iFrame = 0;
439     }
440 #endif  /*  VERBOSE */
441
442   return st->delay;
443 }
444         
445
446 static void
447 eruption_reshape (Display *dpy, Window window, void *closure, 
448                  unsigned int w, unsigned int h)
449 {
450 }
451
452 static Bool
453 eruption_event (Display *dpy, Window window, void *closure, XEvent *event)
454 {
455   return False;
456 }
457
458 static void
459 eruption_free (Display *dpy, Window window, void *closure)
460 {
461 #if 0
462   struct state *st = (struct state *) closure;
463         free( st->pImage->data );
464         XDestroyImage( st->pImage );
465         free( st->aiColorVals );
466         for (i = 0; i < st->iWinHeight; ++i)
467           free( st->fire[i] );
468         free( st->fire );
469         free( st->particles );
470 #endif
471 }
472
473
474 static const char *eruption_defaults [] = {
475   ".background: black",
476   ".foreground: white",
477   "*cycles:   80",
478   "*ncolors:  256",
479   "*delay:    10000",
480   "*particles: 300",
481   "*cooloff: 2",
482   "*gravity: 1",
483   "*heat: 256",
484   0
485 };
486
487 static XrmOptionDescRec eruption_options [] = {
488   { "-ncolors", ".ncolors", XrmoptionSepArg, 0 },
489   { "-delay",   ".delay",   XrmoptionSepArg, 0 },
490   { "-cycles",  ".cycles",  XrmoptionSepArg, 0 },
491   { "-particles",  ".particles",  XrmoptionSepArg, 0 },
492   { "-cooloff",  ".cooloff",  XrmoptionSepArg, 0 },
493   { "-gravity",  ".gravity",  XrmoptionSepArg, 0 },
494   { "-heat",  ".heat",  XrmoptionSepArg, 0 },
495   { 0, 0, 0, 0 }
496 };
497
498
499 XSCREENSAVER_MODULE ("Eruption", eruption)
500
501 /* End of Module - "eruption.c" */
502