http://ftp.x.org/contrib/applications/xscreensaver-3.19.tar.gz
[xscreensaver] / hacks / petri.c
1 /* petri, simulate mold in a petri dish. v2.6
2  * by Dan Bornstein, danfuzz@milk.com
3  * with help from Jamie Zawinski, jwz@jwz.org
4  * Copyright (c) 1992-1999 Dan Bornstein.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation.  No representations are made about the suitability of this
11  * software for any purpose.  It is provided "as is" without express or 
12  * implied warranty.
13  *
14  *
15  * Brief description of options/resources:
16  *
17  * delay: the delay in microseconds between iterations
18  * size: the size of a cell in pixels
19  * count: the number of different kinds of mold (minimum: 2)
20  * diaglim: the age limit for diagonal growth as a multiplier of orthogonal
21  *   growth (minimum: 1, maximum 2). 1 means square growth, 1.414 
22  *   (i.e., sqrt(2)) means approximately circular growth, 2 means diamond
23  *   growth.
24  * anychan: the chance (fraction, between 0 and 1) that at each iteration,
25  *   any new cell will be born
26  * minorchan: the chance (fraction, between 0 and 1) that, given that new
27  *   cells will be added, that only two will be added (a minor cell birth
28  *   event)
29  * instantdeathchan: the chance (fraction, between 0 and 1) that, given
30  *   that death and destruction will happen, that instead of using plague
31  *   cells, death will be instantaneous
32  * minlifespan: the minimum lifespan of a colony (before black death ensues)
33  * maxlifespan: the maximum lifespan of a colony (before black death ensues)
34  * minlifespeed: the minimum speed for living cells as a fraction of the
35  *   maximum possible speed (fraction, between 0 and 1)
36  * maxlifespeed: the maximum speed for living cells as a fraction of the
37  *   maximum possible speed (fraction, between 0 and 1)
38  * mindeathspeed: the minimum speed for black death cells as a fraction of the
39  *   maximum possible speed (fraction, between 0 and 1)
40  * maxdeathspeed: the maximum speed for black death cells as a fraction of the
41  *   maximum possible speed (fraction, between 0 and 1)
42  * originalcolors: if true, count must be 8 or less and the colors are a 
43  *   fixed set of primary and secondary colors (the artist's original choices)
44  *
45  * Interesting settings:
46  *
47  *      petri -originalcolors -size 8
48  *      petri -size 2
49  *      petri -size 8 -diaglim 1.8
50  *      petri -diaglim 1.1
51  *
52  *      petri -count 4 -anychan 0.01 -minorchan 1 \
53  *              -minlifespan 2000 -maxlifespan 5000
54  *
55  *      petri -count 3 -anychan 1 -minlifespan 100000 \ 
56  *              -instantdeathchan 0
57  *
58  *      petri -minlifespeed 0.02 -maxlifespeed 0.03 -minlifespan 1 \
59  *              -maxlifespan 1 -instantdeathchan 0 -minorchan 0 \
60  *              -anychan 0.3 -delay 4000
61  */
62
63 #include <math.h>
64 #include "screenhack.h"
65 #include "spline.h"
66
67 #define FLOAT float
68 #define RAND_FLOAT (((FLOAT) (random() & 0xffff)) / ((FLOAT) 0x10000))
69
70 typedef struct cell_s 
71 {
72     short x;                        /*  0    */
73     short y;                        /*  2    */
74     unsigned char col;              /*  4    */
75     unsigned char isnext;           /*  5    */
76     unsigned char nextcol;          /*  6    */
77                                     /*  7    */
78     struct cell_s *next;            /*  8    */
79     struct cell_s *prev;            /* 12    */
80     struct cell_s *(adj)[3][3];     /* 16    */
81     FLOAT speed;                    /* 52    */
82     FLOAT growth;                   /* 56 60 */
83     FLOAT nextspeed;                /* 60 68 */
84                                     /* 64 76 */
85 } cell;
86
87 static int arr_width;
88 static int arr_height;
89 static int count;
90
91 static cell *arr;
92 static cell *head;
93 static cell *tail;
94 static int blastcount;
95
96 static Display *display;
97 static Window window;
98 static GC *coloredGCs;
99
100 static int windowWidth;
101 static int windowHeight;
102 static int xOffset;
103 static int yOffset;
104 static int xSize;
105 static int ySize;
106
107 static FLOAT orthlim = 1.0;
108 static FLOAT diaglim;
109 static FLOAT anychan;
110 static FLOAT minorchan;
111 static FLOAT instantdeathchan;
112 static int minlifespan;
113 static int maxlifespan;
114 static FLOAT minlifespeed;
115 static FLOAT maxlifespeed;
116 static FLOAT mindeathspeed;
117 static FLOAT maxdeathspeed;
118 static Bool originalcolors;
119
120 static int random_life_value (void)
121 {
122     return (int) ((RAND_FLOAT * (maxlifespan - minlifespan)) + minlifespan);
123 }
124
125 static void setup_random_colormap (XWindowAttributes *xgwa)
126 {
127     XGCValues gcv;
128     int lose = 0;
129     int ncolors = count - 1;
130     int n;
131     XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
132     
133     colors[0].pixel = get_pixel_resource ("background", "Background",
134                                           display, xgwa->colormap);
135     
136     make_random_colormap (display, xgwa->visual, xgwa->colormap,
137                           colors+1, &ncolors, True, True, 0, True);
138     if (ncolors < 1)
139         exit (-1);
140     
141     ncolors++;
142     count = ncolors;
143     
144     memcpy (colors + count, colors, count * sizeof(*colors));
145     colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
146                                               display, xgwa->colormap);
147     
148     for (n = 1; n < count; n++)
149     {
150         int m = n + count;
151         colors[n].red = colors[m].red / 2;
152         colors[n].green = colors[m].green / 2;
153         colors[n].blue = colors[m].blue / 2;
154         
155         if (!XAllocColor (display, xgwa->colormap, &colors[n]))
156         {
157             lose++;
158             colors[n] = colors[m];
159         }
160     }
161
162     if (lose)
163     {
164         fprintf (stderr, 
165                  "%s: unable to allocate %d half-intensity colors.\n",
166                  progname, lose);
167     }
168     
169     for (n = 0; n < count*2; n++) 
170     {
171         gcv.foreground = colors[n].pixel;
172         coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
173     }
174
175     free (colors);
176 }
177
178 static void setup_original_colormap (XWindowAttributes *xgwa)
179 {
180     XGCValues gcv;
181     int lose = 0;
182     int n;
183     XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
184     
185     colors[0].pixel = get_pixel_resource ("background", "Background",
186                                           display, xgwa->colormap);
187
188     colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
189                                               display, xgwa->colormap);
190
191     for (n = 1; n < count; n++)
192     {
193         int m = n + count;
194         colors[n].red =   ((n & 0x01) != 0) * 0x8000;
195         colors[n].green = ((n & 0x02) != 0) * 0x8000;
196         colors[n].blue =  ((n & 0x04) != 0) * 0x8000;
197
198         if (!XAllocColor (display, xgwa->colormap, &colors[n]))
199         {
200             lose++;
201             colors[n] = colors[0];
202         }
203
204         colors[m].red   = colors[n].red + 0x4000;
205         colors[m].green = colors[n].green + 0x4000;
206         colors[m].blue  = colors[n].blue + 0x4000;
207
208         if (!XAllocColor (display, xgwa->colormap, &colors[m]))
209         {
210             lose++;
211             colors[m] = colors[count];
212         }
213     }
214
215     if (lose)
216     {
217         fprintf (stderr, 
218                  "%s: unable to allocate %d colors.\n",
219                  progname, lose);
220     }
221     
222     for (n = 0; n < count*2; n++) 
223     {
224         gcv.foreground = colors[n].pixel;
225         coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
226     }
227
228     free (colors);
229 }
230
231 static void setup_display (void)
232 {
233     XWindowAttributes xgwa;
234     Colormap cmap;
235
236     int cell_size = get_integer_resource ("size", "Integer");
237     if (cell_size < 1) cell_size = 1;
238
239     XGetWindowAttributes (display, window, &xgwa);
240
241     originalcolors = get_boolean_resource ("originalcolors", "Boolean");
242
243     count = get_integer_resource ("count", "Integer");
244     if (count < 2) count = 2;
245     if (count > (1L << (xgwa.depth-1)))
246       count = (1L << (xgwa.depth-1));
247
248     if (originalcolors && (count > 8))
249     {
250         count = 8;
251     }
252
253     coloredGCs = (GC *) calloc (sizeof(GC), count * 2);
254
255     diaglim  = get_float_resource ("diaglim", "Float");
256     if (diaglim < 1.0)
257     {
258         diaglim = 1.0;
259     }
260     else if (diaglim > 2.0)
261     {
262         diaglim = 2.0;
263     }
264     diaglim *= orthlim;
265
266     anychan  = get_float_resource ("anychan", "Float");
267     if (anychan < 0.0)
268     {
269         anychan = 0.0;
270     }
271     else if (anychan > 1.0)
272     {
273         anychan = 1.0;
274     }
275     
276     minorchan = get_float_resource ("minorchan","Float");
277     if (minorchan < 0.0)
278     {
279         minorchan = 0.0;
280     }
281     else if (minorchan > 1.0)
282     {
283         minorchan = 1.0;
284     }
285     
286     instantdeathchan = get_float_resource ("instantdeathchan","Float");
287     if (instantdeathchan < 0.0)
288     {
289         instantdeathchan = 0.0;
290     }
291     else if (instantdeathchan > 1.0)
292     {
293         instantdeathchan = 1.0;
294     }
295
296     minlifespan = get_integer_resource ("minlifespan", "Integer");
297     if (minlifespan < 1)
298     {
299         minlifespan = 1;
300     }
301
302     maxlifespan = get_integer_resource ("maxlifespan", "Integer");
303     if (maxlifespan < minlifespan)
304     {
305         maxlifespan = minlifespan;
306     }
307
308     minlifespeed = get_float_resource ("minlifespeed", "Float");
309     if (minlifespeed < 0.0)
310     {
311         minlifespeed = 0.0;
312     }
313     else if (minlifespeed > 1.0)
314     {
315         minlifespeed = 1.0;
316     }
317
318     maxlifespeed = get_float_resource ("maxlifespeed", "Float");
319     if (maxlifespeed < minlifespeed)
320     {
321         maxlifespeed = minlifespeed;
322     }
323     else if (maxlifespeed > 1.0)
324     {
325         maxlifespeed = 1.0;
326     }
327
328     mindeathspeed = get_float_resource ("mindeathspeed", "Float");
329     if (mindeathspeed < 0.0)
330     {
331         mindeathspeed = 0.0;
332     }
333     else if (mindeathspeed > 1.0)
334     {
335         mindeathspeed = 1.0;
336     }
337
338     maxdeathspeed = get_float_resource ("maxdeathspeed", "Float");
339     if (maxdeathspeed < mindeathspeed)
340     {
341         maxdeathspeed = mindeathspeed;
342     }
343     else if (maxdeathspeed > 1.0)
344     {
345         maxdeathspeed = 1.0;
346     }
347
348     minlifespeed *= diaglim;
349     maxlifespeed *= diaglim;
350     mindeathspeed *= diaglim;
351     maxdeathspeed *= diaglim;
352
353     cmap = xgwa.colormap;
354     
355     windowWidth = xgwa.width;
356     windowHeight = xgwa.height;
357     
358     arr_width = windowWidth / cell_size;
359     arr_height = windowHeight / cell_size;
360
361     xSize = windowWidth / arr_width;
362     ySize = windowHeight / arr_height;
363     if (xSize > ySize)
364     {
365         xSize = ySize;
366     }
367     else
368     {
369         ySize = xSize;
370     }
371     
372     xOffset = (windowWidth - (arr_width * xSize)) / 2;
373     yOffset = (windowHeight - (arr_height * ySize)) / 2;
374
375     if (originalcolors)
376     {
377         setup_original_colormap (&xgwa);
378     }
379     else
380     {
381         setup_random_colormap (&xgwa);
382     }
383 }
384
385 static void drawblock (int x, int y, unsigned char c)
386 {
387   if (xSize == 1 && ySize == 1)
388     XDrawPoint (display, window, coloredGCs[c], x + xOffset, y + yOffset);
389   else
390     XFillRectangle (display, window, coloredGCs[c],
391                     x * xSize + xOffset, y * ySize + yOffset,
392                     xSize, ySize);
393 }
394
395 static void setup_arr (void)
396 {
397     int x, y;
398     int i, j;
399     int a, b;
400
401     if (arr != NULL)
402     {
403         free (arr);
404     }
405
406     XFillRectangle (display, window, coloredGCs[0], 0, 0, 
407                     windowWidth, windowHeight);
408
409     arr = (cell *) calloc (sizeof(cell), arr_width * arr_height);  
410     if (!arr)
411       {
412         fprintf (stderr, "%s: out of memory allocating %dx%d grid\n",
413                  progname, arr_width, arr_height);
414         exit (1);
415       }
416
417     for (y = 0; y < arr_height; y++)
418     {
419       int row = y * arr_width;
420         for (x = 0; x < arr_width; x++) 
421         {
422             arr[row+x].x = x;
423             arr[row+x].y = y;
424             arr[row+x].speed = 0.0;
425             arr[row+x].growth = 0.0;
426             arr[row+x].col = 0;
427             arr[row+x].isnext = 0;
428             arr[row+x].next = 0;
429             arr[row+x].prev = 0;
430             for (i = 0; i < 3; i++) 
431             {
432                 a = x + i - 1;
433                 if (a < 0) a = arr_width - 1;
434                 else if (a >= arr_width) a = 0;
435                 for (j = 0; j < 3; j++) 
436                 {
437                     b = y + j - 1;
438                     if (b < 0) b = arr_height - 1;
439                     else if (b >= arr_height) b = 0;
440                     arr[row+x].adj[i][j] = &arr[(b * arr_width) + a];
441                 }
442             }
443         }
444     }
445
446     if (head == NULL)
447     {
448         head = (cell *) malloc (sizeof (cell));
449     }
450     
451     if (tail == NULL)
452     {
453         tail = (cell *) malloc (sizeof (cell));
454     }
455
456     head->next = tail;
457     head->prev = head;
458     tail->next = tail;
459     tail->prev = head;
460
461     blastcount = random_life_value ();
462 }
463
464 static void newcell (cell *c, unsigned char col, FLOAT sp)
465 {
466     if (! c) return;
467     
468     if (c->col == col) return;
469     
470     c->nextcol = col;
471     c->nextspeed = sp;
472     c->isnext = 1;
473     
474     if (c->prev == 0) {
475         c->next = head->next;
476         c->prev = head;
477         head->next = c;
478         c->next->prev = c;
479     }
480 }
481
482 static void killcell (cell *c)
483 {
484     c->prev->next = c->next;
485     c->next->prev = c->prev;
486     c->prev = 0;
487     c->speed = 0.0;
488     drawblock (c->x, c->y, c->col);
489 }
490
491
492 static void randblip (int doit)
493 {
494     int n;
495     int b = 0;
496     if (!doit 
497         && (blastcount-- >= 0) 
498         && (RAND_FLOAT > anychan))
499     {
500         return;
501     }
502     
503     if (blastcount < 0) 
504     {
505         b = 1;
506         n = 2;
507         blastcount = random_life_value ();
508         if (RAND_FLOAT < instantdeathchan)
509         {
510             /* clear everything every so often to keep from getting into a
511              * rut */
512             setup_arr ();
513             b = 0;
514         }
515     }
516     else if (RAND_FLOAT <= minorchan) 
517     {
518         n = 2;
519     }
520     else 
521     {
522         n = random () % 3 + 3;
523     }
524     
525     while (n--) 
526     {
527         int x = random () % arr_width;
528         int y = random () % arr_height;
529         int c;
530         FLOAT s;
531         if (b)
532         {
533             c = 0;
534             s = RAND_FLOAT * (maxdeathspeed - mindeathspeed) + mindeathspeed;
535         }
536         else
537         {
538             c = (random () % (count-1)) + 1;
539             s = RAND_FLOAT * (maxlifespeed - minlifespeed) + minlifespeed;
540         }
541         newcell (&arr[y * arr_width + x], c, s);
542     }
543 }
544
545 static void update (void)
546 {
547     cell *a;
548     
549     for (a = head->next; a != tail; a = a->next) 
550     {
551         if (a->speed == 0) continue;
552         a->growth += a->speed;
553         if (a->growth >= orthlim) 
554         {
555             newcell (a->adj[0][1], a->col, a->speed);
556             newcell (a->adj[2][1], a->col, a->speed);
557             newcell (a->adj[1][0], a->col, a->speed);
558             newcell (a->adj[1][2], a->col, a->speed);
559         }
560         if (a->growth >= diaglim) 
561         {
562             newcell (a->adj[0][0], a->col, a->speed);
563             newcell (a->adj[0][2], a->col, a->speed);
564             newcell (a->adj[2][0], a->col, a->speed);
565             newcell (a->adj[2][2], a->col, a->speed);
566             killcell (a);
567         }
568     }
569     
570     randblip ((head->next) == tail);
571     
572     for (a = head->next; a != tail; a = a->next)
573     {
574         if (a->isnext) 
575         {
576             a->isnext = 0;
577             a->speed = a->nextspeed;
578             a->growth = 0.0;
579             a->col = a->nextcol;
580             drawblock (a->x, a->y, a->col + count);
581         }
582     }
583 }
584
585 \f
586 char *progclass = "Petri";
587
588 char *defaults [] = {
589   ".background:         black",
590   ".foreground:         white",
591   "*delay:              10000",
592   "*count:              8",
593   "*size:               4",
594   "*diaglim:            1.414",
595   "*anychan:            0.0015",
596   "*minorchan:          0.5",
597   "*instantdeathchan:   0.2",
598   "*minlifespan:        500",
599   "*maxlifespan:        1500",
600   "*minlifespeed:       0.04",
601   "*maxlifespeed:       0.13",
602   "*mindeathspeed:      0.42",
603   "*maxdeathspeed:      0.46",
604   "*originalcolors:     false",
605     0
606 };
607
608 XrmOptionDescRec options [] = {
609   { "-delay",            ".delay",              XrmoptionSepArg, 0 },
610   { "-size",             ".size",               XrmoptionSepArg, 0 },
611   { "-count",            ".count",              XrmoptionSepArg, 0 },
612   { "-diaglim",          ".diaglim",            XrmoptionSepArg, 0 },
613   { "-anychan",          ".anychan",            XrmoptionSepArg, 0 },
614   { "-minorchan",        ".minorchan",          XrmoptionSepArg, 0 },
615   { "-instantdeathchan", ".instantdeathchan",   XrmoptionSepArg, 0 },
616   { "-minlifespan",      ".minlifespan",        XrmoptionSepArg, 0 },
617   { "-maxlifespan",      ".maxlifespan",        XrmoptionSepArg, 0 },
618   { "-minlifespeed",     ".minlifespeed",       XrmoptionSepArg, 0 },
619   { "-maxlifespeed",     ".maxlifespeed",       XrmoptionSepArg, 0 },
620   { "-mindeathspeed",    ".mindeathspeed",      XrmoptionSepArg, 0 },
621   { "-maxdeathspeed",    ".maxdeathspeed",      XrmoptionSepArg, 0 },
622   { "-originalcolors",   ".originalcolors",     XrmoptionNoArg,  "true" },
623   { 0, 0, 0, 0 }
624 };
625
626 void screenhack (Display *dpy, Window win)
627 {
628     int delay = get_integer_resource ("delay", "Delay");
629     display = dpy;
630     window = win;
631     setup_display ();
632     
633     setup_arr ();
634     
635     randblip (1);
636     
637     for (;;) 
638     {
639         update ();
640         XSync (dpy, False);
641         screenhack_handle_events (dpy);
642         usleep (delay);
643     }
644 }
645
646