f867b2906aa0462e7e5946397f42b5d6d98cfc2e
[xscreensaver] / hacks / metaballs.c
1 /* MetaBalls, 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 - "metaballs.c"
12  *
13  * [01/24/03] - W.P. van Paassen: Additional features
14  * [12/29/02] - W.P. van Paassen: Port to X for use with XScreenSaver, the shadebob hack by Shane Smit was used as a template
15  * [12/26/02] - 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 #include <X11/Xutil.h>
21
22 /*#define VERBOSE*/ 
23
24 char *progclass = "Metaballs";
25
26 char *defaults [] = {
27   ".background: black",
28   ".foreground: white",
29   "*color:    random",
30   "*count:    10",
31   "*cycles:   1000",
32   "*ncolors:  256",
33   "*delay:    5000",
34   "*radius:   100",
35   "*delta:   3",
36   0
37 };
38
39 XrmOptionDescRec options [] = {
40   { "-color",   ".color",   XrmoptionSepArg, 0 },
41   { "-ncolors", ".ncolors", XrmoptionSepArg, 0 },
42   { "-count",   ".count",   XrmoptionSepArg, 0 },
43   { "-delay",   ".delay",   XrmoptionSepArg, 0 },
44   { "-cycles",  ".cycles",  XrmoptionSepArg, 0 },
45   { "-radius",  ".radius",  XrmoptionSepArg, 0 },
46   { "-delta",  ".delta",  XrmoptionSepArg, 0 },
47   { 0, 0, 0, 0 }
48 };
49
50 static unsigned short iWinWidth, iWinHeight;
51 static char *sColor;
52
53 /*blob structure*/
54 typedef struct 
55 {
56   short xpos,ypos;
57 } BLOB;
58
59 static unsigned int nBlobCount;
60 static unsigned char radius;
61 static unsigned char delta;
62 static unsigned char dradius;
63 static unsigned short sradius;
64 static unsigned char **blob;
65 static BLOB *blobs;
66 static unsigned char **blub;
67
68 #undef BELLRAND
69 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
70
71 static void init_blob(BLOB *blob)
72 {
73   blob->xpos = iWinWidth/4  + BELLRAND(iWinWidth/2)  - radius;
74   blob->ypos = iWinHeight/4 + BELLRAND(iWinHeight/2) - radius;
75 }
76
77 static void Execute( Display *pDisplay,
78                      Window MainWindow,
79                      GC *pGC, XImage *pImage,
80                      signed short iColorCount, unsigned long *aiColorVals )
81 {
82         unsigned int i, j, k;
83
84         /* clear blub array */
85         for (i = 0; i < iWinHeight; ++i)
86           memset(blub[i], 0, iWinWidth * sizeof(unsigned char));
87
88         /* move blobs */
89         for (i = 0; i < nBlobCount; i++)
90         {
91           blobs[i].xpos += -delta + (int)((delta + .5f) * frand(2.0));
92           blobs[i].ypos += -delta + (int)((delta + .5f) * frand(2.0));
93         }
94
95         /* draw blobs to blub array */
96         for (k = 0; k < nBlobCount; ++k)
97           { 
98             if (blobs[k].ypos > -dradius && blobs[k].xpos > -dradius && blobs[k].ypos < iWinHeight && blobs[k].xpos < iWinWidth)
99               {
100                 for (i = 0; i < dradius; ++i)
101                   {
102                     if (blobs[k].ypos + i >= 0 && blobs[k].ypos + i < iWinHeight)
103                       {
104                         for (j = 0; j < dradius; ++j)
105                           {
106                             if (blobs[k].xpos + j >= 0 && blobs[k].xpos + j < iWinWidth)
107                               {
108                                 if (blub[blobs[k].ypos + i][blobs[k].xpos + j] < iColorCount)
109                                   {
110                                     if (blub[blobs[k].ypos + i][blobs[k].xpos + j] + blob[i][j] > iColorCount)
111                                       blub[blobs[k].ypos + i][blobs[k].xpos + j] = iColorCount;
112                                     else 
113                                       blub[blobs[k].ypos + i][blobs[k].xpos + j] += blob[i][j];     
114                                   }
115                               }
116                           }
117                       }
118                   }
119               }
120             else
121               init_blob(blobs + k);
122           }
123
124         memset( pImage->data, 0, pImage->bytes_per_line * pImage->height);
125
126         /* draw blub array to screen */
127         for (i = 0; i < iWinHeight; ++i)
128           {
129             for (j = 0; j < iWinWidth; ++j)
130               {
131                 if (aiColorVals[blub[i][j]] > 0)
132                   XPutPixel( pImage, j, i, aiColorVals[blub[i][j]] );
133               }
134           }
135
136         XPutImage( pDisplay, MainWindow, *pGC, pImage,
137                    0, 0, 0, 0, iWinWidth, iWinHeight );
138         XSync( pDisplay, False );
139 }
140
141 static unsigned long * SetPalette(Display *pDisplay, Window Win, char *sColor, signed short *piColorCount )
142 {
143         XWindowAttributes XWinAttribs;
144         XColor Color, *aColors;
145         unsigned long *aiColorVals;
146         signed short iColor;
147         float nHalfColors;
148         
149         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
150         
151         Color.red =   random() % 0xFFFF;
152         Color.green = random() % 0xFFFF;
153         Color.blue =  random() % 0xFFFF;
154
155         if( strcasecmp( sColor, "random" ) && !XParseColor( pDisplay, XWinAttribs.colormap, sColor, &Color ) )
156                 fprintf( stderr, "%s: color %s not found in database. Choosing to random...\n", progname, sColor );
157
158 #ifdef VERBOSE
159         printf( "%s: Base color (RGB): <%d, %d, %d>\n", progclass, Color.red, Color.green, Color.blue );
160 #endif  /*  VERBOSE */
161
162         *piColorCount = get_integer_resource( "ncolors", "Integer" );
163         if( *piColorCount <   2 )       *piColorCount = 2;
164         if( *piColorCount > 255 )       *piColorCount = 255;
165
166         aColors    = calloc( *piColorCount, sizeof(XColor) );
167         aiColorVals = calloc( *piColorCount, sizeof(unsigned long) );
168         
169         for( iColor=0; iColor<*piColorCount; iColor++ )
170         {
171                 nHalfColors = *piColorCount / 2.0F;
172                 /* Black -> Base Color */
173                 if( iColor < (*piColorCount/2) )
174                 {
175                         aColors[ iColor ].red   = ( Color.red   / nHalfColors ) * iColor;
176                         aColors[ iColor ].green = ( Color.green / nHalfColors ) * iColor;
177                         aColors[ iColor ].blue  = ( Color.blue  / nHalfColors ) * iColor;
178                 }
179                 /* Base Color -> White */
180                 else
181                 {
182                         aColors[ iColor ].red   = ( ( ( 0xFFFF - Color.red )   / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.red;
183                         aColors[ iColor ].green = ( ( ( 0xFFFF - Color.green ) / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.green;
184                         aColors[ iColor ].blue  = ( ( ( 0xFFFF - Color.blue )  / nHalfColors ) * ( iColor - nHalfColors ) ) + Color.blue;
185                 }
186
187                 if( !XAllocColor( pDisplay, XWinAttribs.colormap, &aColors[ iColor ] ) )
188                 {
189                         /* start all over with less colors */   
190                         XFreeColors( pDisplay, XWinAttribs.colormap, aiColorVals, iColor, 0 );
191                         free( aColors );
192                         free( aiColorVals );
193                         (*piColorCount)--;
194
195                         if (*piColorCount < 6)
196                           {
197                             fprintf (stderr, "%s: insufficient colors!\n",
198                                      progname);
199                             exit (1);
200                           }
201
202                         aColors     = calloc( *piColorCount, sizeof(XColor) );
203                         aiColorVals = calloc( *piColorCount, sizeof(unsigned long) );
204                         iColor = -1;
205                 }
206                 else
207                         aiColorVals[ iColor ] = aColors[ iColor ].pixel;
208         }
209
210         free( aColors );
211
212         XSetWindowBackground( pDisplay, Win, aiColorVals[ 0 ] );
213
214         return aiColorVals;
215 }
216
217
218 static void Initialize( Display *pDisplay, Window Win, GC *pGC, XImage **ppImage )
219 {
220         XGCValues gcValues;
221         XWindowAttributes XWinAttribs;
222         int iBitsPerPixel, i, j;
223         unsigned int distance_squared;
224         float fraction;
225
226         /* Create the Image for drawing */
227         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
228
229         /* Find the preferred bits-per-pixel. (jwz) */
230         {
231                 int pfvc = 0;
232                 XPixmapFormatValues *pfv = XListPixmapFormats( pDisplay, &pfvc );
233                 for( i=0; i<pfvc; i++ )
234                         if( pfv[ i ].depth == XWinAttribs.depth )
235                         {
236                                 iBitsPerPixel = pfv[ i ].bits_per_pixel;
237                                 break;
238                         }
239                 if( pfv )
240                         XFree (pfv);
241         }
242
243         /*  Create the GC. */
244         *pGC = XCreateGC( pDisplay, Win, 0, &gcValues );
245
246         *ppImage = XCreateImage( pDisplay, XWinAttribs.visual, XWinAttribs.depth, ZPixmap, 0, NULL,
247                                                           XWinAttribs.width, XWinAttribs.height, BitmapPad( pDisplay ), 0 );
248         (*ppImage)->data = calloc((*ppImage)->bytes_per_line, (*ppImage)->height);
249
250         iWinWidth = XWinAttribs.width;
251         iWinHeight = XWinAttribs.height;
252
253         /*  Get the base color. */
254         sColor = get_string_resource( "color", "Color" );
255
256         /*  Get the delta. */
257         delta = get_integer_resource( "delta", "Integer" );
258         if (delta < 1)
259           delta = 1;
260         else if (delta > 20)
261           delta = 20;
262         
263         /*  Get the radius. */
264         radius = get_integer_resource( "radius", "Integer" );
265         if (radius < 2)
266           radius = 2;
267         if (radius > 100)
268           radius = 100;
269         
270         radius = (radius / 100.0) * (iWinHeight >> 3);
271         if (radius >= 128) /* should use UCHAR_MAX? */
272           radius = 127; /* dradius should fit in u_char */
273
274         dradius = radius * 2;
275         sradius = radius * radius;
276
277         /* create blob */
278         blob = malloc ( dradius * sizeof(unsigned char*));
279         for (i = 0; i < dradius; ++i)
280           blob[i] = malloc( dradius * sizeof(unsigned char));
281
282         /* create blub array */
283         blub = malloc( iWinHeight * sizeof(unsigned char*));
284         for (i = 0; i < iWinHeight; ++i)
285           blub[i] = malloc( iWinWidth * sizeof(unsigned char));
286
287         /* create blob */
288         for (i = -radius; i < radius; ++i)
289           {
290             for (j = -radius; j < radius; ++j)
291               {
292                 distance_squared = i * i + j * j;
293                 if (distance_squared <= sradius)
294                   {
295                     /* compute density */     
296                     fraction = (float)distance_squared / (float)sradius;
297                     blob[i + radius][j + radius] = pow((1.0 - (fraction * fraction)),4.0) * 255.0;
298                   }
299                 else
300                   {
301                     blob[i + radius][j + radius] = 0;
302                   }
303               }    
304           }
305         
306         for (i = 0; i < nBlobCount; i++)
307           {
308             init_blob(blobs + i);
309           }
310 }
311
312 void screenhack(Display *pDisplay, Window Win )
313 {
314         GC gc;
315         signed short iColorCount = 0;
316         unsigned long *aiColorVals = NULL;
317         XImage *pImage = NULL;
318 #ifdef VERBOSE
319         time_t nTime = time( NULL );
320         unsigned short iFrame = 0;
321 #endif  /*  VERBOSE */
322         int delay, cycles, i;
323
324         nBlobCount = get_integer_resource( "count", "Integer" );
325         if( nBlobCount > 255 ) nBlobCount = 255;
326         if( nBlobCount <  2 ) nBlobCount = 2;
327
328         if( ( blobs = calloc( nBlobCount, sizeof(BLOB) ) ) == NULL )
329         {
330                 fprintf( stderr, "%s: Could not allocate %d Blobs\n", progclass, nBlobCount );
331                 return;
332         }
333 #ifdef VERBOSE 
334         printf( "%s: Allocated %d Blobs\n", progclass, nBlobCount );
335 #endif  /*  VERBOSE */
336
337         Initialize( pDisplay, Win, &gc, &pImage );
338
339         delay = get_integer_resource( "delay", "Integer" );
340         cycles = get_integer_resource( "cycles", "Integer" );
341         i = cycles;
342
343         while( 1 )
344         {
345                 screenhack_handle_events( pDisplay );
346
347                 if( i++ >= cycles )
348                 {
349                         XWindowAttributes XWinAttribs;
350                         XGetWindowAttributes( pDisplay, Win, &XWinAttribs );
351
352                         memset( pImage->data, 0, pImage->bytes_per_line * pImage->height );
353                         XFreeColors( pDisplay, XWinAttribs.colormap, aiColorVals, iColorCount, 0 );
354                         free( aiColorVals );
355                         aiColorVals = SetPalette( pDisplay, Win, sColor, &iColorCount );
356                         XClearWindow( pDisplay, Win );
357                         for (i = 0; i < nBlobCount; i++)
358                           {
359                             init_blob(blobs + i);
360                           }
361                         i = 0;
362                 }
363
364                 Execute( pDisplay, Win, &gc, pImage, iColorCount - 1, aiColorVals );
365
366                 if( delay && !(i % 4) )
367                         usleep(delay);
368
369 #ifdef VERBOSE
370                 iFrame++;
371                 if( nTime - time( NULL ) )
372                 {
373                         printf( "%s: %d FPS\n", progclass, iFrame );
374                         nTime = time( NULL );
375                         iFrame = 0;
376                 }
377 #endif  /*  VERBOSE */
378         }
379
380         free( pImage->data );
381         XDestroyImage( pImage );
382         free( aiColorVals );
383         free( blobs );
384         for (i = 0; i < iWinHeight; ++i)
385           free( blub[i] );
386         free( blub );
387         for (i = 0; i < dradius; ++i)
388           free( blob[i] );
389         free( blob );
390 }
391
392
393 /* End of Module - "metaballs.c" */
394