http://ftp.x.org/contrib/applications/xscreensaver-3.20.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     FLOAT speed;                    /* 16    - */
81     FLOAT growth;                   /* 20 24   */
82     FLOAT nextspeed;                /* 24 32 - */
83                                     /* 28 40   */
84 } cell;
85
86 static int arr_width;
87 static int arr_height;
88 static int count;
89
90 static cell *arr;
91 static cell *head;
92 static cell *tail;
93 static int blastcount;
94
95 static Display *display;
96 static Window window;
97 static GC *coloredGCs;
98
99 static int windowWidth;
100 static int windowHeight;
101 static int xOffset;
102 static int yOffset;
103 static int xSize;
104 static int ySize;
105
106 static FLOAT orthlim = 1.0;
107 static FLOAT diaglim;
108 static FLOAT anychan;
109 static FLOAT minorchan;
110 static FLOAT instantdeathchan;
111 static int minlifespan;
112 static int maxlifespan;
113 static FLOAT minlifespeed;
114 static FLOAT maxlifespeed;
115 static FLOAT mindeathspeed;
116 static FLOAT maxdeathspeed;
117 static Bool originalcolors;
118
119 static int random_life_value (void)
120 {
121     return (int) ((RAND_FLOAT * (maxlifespan - minlifespan)) + minlifespan);
122 }
123
124 static void setup_random_colormap (XWindowAttributes *xgwa)
125 {
126     XGCValues gcv;
127     int lose = 0;
128     int ncolors = count - 1;
129     int n;
130     XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
131     
132     colors[0].pixel = get_pixel_resource ("background", "Background",
133                                           display, xgwa->colormap);
134     
135     make_random_colormap (display, xgwa->visual, xgwa->colormap,
136                           colors+1, &ncolors, True, True, 0, True);
137     if (ncolors < 1)
138       {
139         fprintf (stderr, "%s: couldn't allocate any colors\n", progname);
140         exit (-1);
141       }
142     
143     ncolors++;
144     count = ncolors;
145     
146     memcpy (colors + count, colors, count * sizeof(*colors));
147     colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
148                                               display, xgwa->colormap);
149     
150     for (n = 1; n < count; n++)
151     {
152         int m = n + count;
153         colors[n].red = colors[m].red / 2;
154         colors[n].green = colors[m].green / 2;
155         colors[n].blue = colors[m].blue / 2;
156         
157         if (!XAllocColor (display, xgwa->colormap, &colors[n]))
158         {
159             lose++;
160             colors[n] = colors[m];
161         }
162     }
163
164     if (lose)
165     {
166         fprintf (stderr, 
167                  "%s: unable to allocate %d half-intensity colors.\n",
168                  progname, lose);
169     }
170     
171     for (n = 0; n < count*2; n++) 
172     {
173         gcv.foreground = colors[n].pixel;
174         coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
175     }
176
177     free (colors);
178 }
179
180 static void setup_original_colormap (XWindowAttributes *xgwa)
181 {
182     XGCValues gcv;
183     int lose = 0;
184     int n;
185     XColor *colors = (XColor *) calloc (sizeof(*colors), count*2);
186     
187     colors[0].pixel = get_pixel_resource ("background", "Background",
188                                           display, xgwa->colormap);
189
190     colors[count].pixel = get_pixel_resource ("foreground", "Foreground",
191                                               display, xgwa->colormap);
192
193     for (n = 1; n < count; n++)
194     {
195         int m = n + count;
196         colors[n].red =   ((n & 0x01) != 0) * 0x8000;
197         colors[n].green = ((n & 0x02) != 0) * 0x8000;
198         colors[n].blue =  ((n & 0x04) != 0) * 0x8000;
199
200         if (!XAllocColor (display, xgwa->colormap, &colors[n]))
201         {
202             lose++;
203             colors[n] = colors[0];
204         }
205
206         colors[m].red   = colors[n].red + 0x4000;
207         colors[m].green = colors[n].green + 0x4000;
208         colors[m].blue  = colors[n].blue + 0x4000;
209
210         if (!XAllocColor (display, xgwa->colormap, &colors[m]))
211         {
212             lose++;
213             colors[m] = colors[count];
214         }
215     }
216
217     if (lose)
218     {
219         fprintf (stderr, 
220                  "%s: unable to allocate %d colors.\n",
221                  progname, lose);
222     }
223     
224     for (n = 0; n < count*2; n++) 
225     {
226         gcv.foreground = colors[n].pixel;
227         coloredGCs[n] = XCreateGC (display, window, GCForeground, &gcv);
228     }
229
230     free (colors);
231 }
232
233 static void setup_display (void)
234 {
235     XWindowAttributes xgwa;
236     Colormap cmap;
237
238     int cell_size = get_integer_resource ("size", "Integer");
239     int osize, alloc_size, oalloc;
240     int mem_throttle = 0;
241     char *s;
242
243     if (cell_size < 1) cell_size = 1;
244
245     osize = cell_size;
246
247     s = get_string_resource ("memThrottle", "MemThrottle");
248     if (s)
249       {
250         int n;
251         char c;
252         if (1 == sscanf (s, " %d M %c", &n, &c) ||
253             1 == sscanf (s, " %d m %c", &n, &c))
254           mem_throttle = n * (1 << 20);
255         else if (1 == sscanf (s, " %d K %c", &n, &c) ||
256                  1 == sscanf (s, " %d k %c", &n, &c))
257           mem_throttle = n * (1 << 10);
258         else if (1 == sscanf (s, " %d %c", &n, &c))
259           mem_throttle = n;
260         else
261           {
262             fprintf (stderr, "%s: invalid memThrottle \"%s\" (try \"10M\")\n",
263                      progname, s);
264             exit (1);
265           }
266         
267         free (s);
268       }
269
270     XGetWindowAttributes (display, window, &xgwa);
271
272     originalcolors = get_boolean_resource ("originalcolors", "Boolean");
273
274     count = get_integer_resource ("count", "Integer");
275     if (count < 2) count = 2;
276     if (count > (1L << (xgwa.depth-1)))
277       count = (1L << (xgwa.depth-1));
278
279     if (originalcolors && (count > 8))
280     {
281         count = 8;
282     }
283
284     coloredGCs = (GC *) calloc (sizeof(GC), count * 2);
285
286     diaglim  = get_float_resource ("diaglim", "Float");
287     if (diaglim < 1.0)
288     {
289         diaglim = 1.0;
290     }
291     else if (diaglim > 2.0)
292     {
293         diaglim = 2.0;
294     }
295     diaglim *= orthlim;
296
297     anychan  = get_float_resource ("anychan", "Float");
298     if (anychan < 0.0)
299     {
300         anychan = 0.0;
301     }
302     else if (anychan > 1.0)
303     {
304         anychan = 1.0;
305     }
306     
307     minorchan = get_float_resource ("minorchan","Float");
308     if (minorchan < 0.0)
309     {
310         minorchan = 0.0;
311     }
312     else if (minorchan > 1.0)
313     {
314         minorchan = 1.0;
315     }
316     
317     instantdeathchan = get_float_resource ("instantdeathchan","Float");
318     if (instantdeathchan < 0.0)
319     {
320         instantdeathchan = 0.0;
321     }
322     else if (instantdeathchan > 1.0)
323     {
324         instantdeathchan = 1.0;
325     }
326
327     minlifespan = get_integer_resource ("minlifespan", "Integer");
328     if (minlifespan < 1)
329     {
330         minlifespan = 1;
331     }
332
333     maxlifespan = get_integer_resource ("maxlifespan", "Integer");
334     if (maxlifespan < minlifespan)
335     {
336         maxlifespan = minlifespan;
337     }
338
339     minlifespeed = get_float_resource ("minlifespeed", "Float");
340     if (minlifespeed < 0.0)
341     {
342         minlifespeed = 0.0;
343     }
344     else if (minlifespeed > 1.0)
345     {
346         minlifespeed = 1.0;
347     }
348
349     maxlifespeed = get_float_resource ("maxlifespeed", "Float");
350     if (maxlifespeed < minlifespeed)
351     {
352         maxlifespeed = minlifespeed;
353     }
354     else if (maxlifespeed > 1.0)
355     {
356         maxlifespeed = 1.0;
357     }
358
359     mindeathspeed = get_float_resource ("mindeathspeed", "Float");
360     if (mindeathspeed < 0.0)
361     {
362         mindeathspeed = 0.0;
363     }
364     else if (mindeathspeed > 1.0)
365     {
366         mindeathspeed = 1.0;
367     }
368
369     maxdeathspeed = get_float_resource ("maxdeathspeed", "Float");
370     if (maxdeathspeed < mindeathspeed)
371     {
372         maxdeathspeed = mindeathspeed;
373     }
374     else if (maxdeathspeed > 1.0)
375     {
376         maxdeathspeed = 1.0;
377     }
378
379     minlifespeed *= diaglim;
380     maxlifespeed *= diaglim;
381     mindeathspeed *= diaglim;
382     maxdeathspeed *= diaglim;
383
384     cmap = xgwa.colormap;
385     
386     windowWidth = xgwa.width;
387     windowHeight = xgwa.height;
388     
389     arr_width = windowWidth / cell_size;
390     arr_height = windowHeight / cell_size;
391
392     alloc_size = sizeof(cell) * arr_width * arr_height;
393     oalloc = alloc_size;
394
395     if (mem_throttle > 0)
396       while (cell_size < windowWidth/10 &&
397              cell_size < windowHeight/10 &&
398              alloc_size > mem_throttle)
399         {
400           cell_size++;
401           arr_width = windowWidth / cell_size;
402           arr_height = windowHeight / cell_size;
403           alloc_size = sizeof(cell) * arr_width * arr_height;
404         }
405
406     if (osize != cell_size)
407       {
408         static int warned = 0;
409         if (!warned)
410           {
411             fprintf (stderr,
412              "%s: throttling cell size from %d to %d because of %dM limit.\n",
413                      progname, osize, cell_size, mem_throttle / (1 << 20));
414             fprintf (stderr, "%s: %dx%dx%d = %.1fM, %dx%dx%d = %.1fM.\n",
415                      progname,
416                      windowWidth, windowHeight, osize,
417                      ((float) oalloc) / (1 << 20),
418                      windowWidth, windowHeight, cell_size,
419                      ((float) alloc_size) / (1 << 20));
420             warned = 1;
421           }
422       }
423
424     xSize = windowWidth / arr_width;
425     ySize = windowHeight / arr_height;
426     if (xSize > ySize)
427     {
428         xSize = ySize;
429     }
430     else
431     {
432         ySize = xSize;
433     }
434     
435     xOffset = (windowWidth - (arr_width * xSize)) / 2;
436     yOffset = (windowHeight - (arr_height * ySize)) / 2;
437
438     if (originalcolors)
439     {
440         setup_original_colormap (&xgwa);
441     }
442     else
443     {
444         setup_random_colormap (&xgwa);
445     }
446 }
447
448 static void drawblock (int x, int y, unsigned char c)
449 {
450   if (xSize == 1 && ySize == 1)
451     XDrawPoint (display, window, coloredGCs[c], x + xOffset, y + yOffset);
452   else
453     XFillRectangle (display, window, coloredGCs[c],
454                     x * xSize + xOffset, y * ySize + yOffset,
455                     xSize, ySize);
456 }
457
458 static void setup_arr (void)
459 {
460     int x, y;
461
462     if (arr != NULL)
463     {
464         free (arr);
465     }
466
467     XFillRectangle (display, window, coloredGCs[0], 0, 0, 
468                     windowWidth, windowHeight);
469
470     arr = (cell *) calloc (sizeof(cell), arr_width * arr_height);  
471     if (!arr)
472       {
473         fprintf (stderr, "%s: out of memory allocating %dx%d grid\n",
474                  progname, arr_width, arr_height);
475         exit (1);
476       }
477
478     for (y = 0; y < arr_height; y++)
479     {
480       int row = y * arr_width;
481         for (x = 0; x < arr_width; x++) 
482         {
483             arr[row+x].x = x;
484             arr[row+x].y = y;
485             arr[row+x].speed = 0.0;
486             arr[row+x].growth = 0.0;
487             arr[row+x].col = 0;
488             arr[row+x].isnext = 0;
489             arr[row+x].next = 0;
490             arr[row+x].prev = 0;
491         }
492     }
493
494     if (head == NULL)
495     {
496         head = (cell *) malloc (sizeof (cell));
497     }
498     
499     if (tail == NULL)
500     {
501         tail = (cell *) malloc (sizeof (cell));
502     }
503
504     head->next = tail;
505     head->prev = head;
506     tail->next = tail;
507     tail->prev = head;
508
509     blastcount = random_life_value ();
510 }
511
512 static void newcell (cell *c, unsigned char col, FLOAT sp)
513 {
514     if (! c) return;
515     
516     if (c->col == col) return;
517     
518     c->nextcol = col;
519     c->nextspeed = sp;
520     c->isnext = 1;
521     
522     if (c->prev == 0) {
523         c->next = head->next;
524         c->prev = head;
525         head->next = c;
526         c->next->prev = c;
527     }
528 }
529
530 static void killcell (cell *c)
531 {
532     c->prev->next = c->next;
533     c->next->prev = c->prev;
534     c->prev = 0;
535     c->speed = 0.0;
536     drawblock (c->x, c->y, c->col);
537 }
538
539
540 static void randblip (int doit)
541 {
542     int n;
543     int b = 0;
544     if (!doit 
545         && (blastcount-- >= 0) 
546         && (RAND_FLOAT > anychan))
547     {
548         return;
549     }
550     
551     if (blastcount < 0) 
552     {
553         b = 1;
554         n = 2;
555         blastcount = random_life_value ();
556         if (RAND_FLOAT < instantdeathchan)
557         {
558             /* clear everything every so often to keep from getting into a
559              * rut */
560             setup_arr ();
561             b = 0;
562         }
563     }
564     else if (RAND_FLOAT <= minorchan) 
565     {
566         n = 2;
567     }
568     else 
569     {
570         n = random () % 3 + 3;
571     }
572     
573     while (n--) 
574     {
575         int x = random () % arr_width;
576         int y = random () % arr_height;
577         int c;
578         FLOAT s;
579         if (b)
580         {
581             c = 0;
582             s = RAND_FLOAT * (maxdeathspeed - mindeathspeed) + mindeathspeed;
583         }
584         else
585         {
586             c = (random () % (count-1)) + 1;
587             s = RAND_FLOAT * (maxlifespeed - minlifespeed) + minlifespeed;
588         }
589         newcell (&arr[y * arr_width + x], c, s);
590     }
591 }
592
593 static void update (void)
594 {
595     cell *a;
596     
597     for (a = head->next; a != tail; a = a->next) 
598     {
599         static XPoint coords1[] = {{-1,  0}, { 1, 0}, {0, -1}, {0, 1}};
600         static XPoint coords2[] = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
601         XPoint *coords = 0;
602         int i;
603
604         if (a->speed == 0) continue;
605         a->growth += a->speed;
606         if (a->growth >= orthlim) 
607           coords = coords1;
608
609         if (a->growth >= diaglim) 
610           coords = coords2;
611
612         if (coords)
613           for (i = 0; i < 4; i++)
614             {
615               int x = a->x + coords[i].x;
616               int y = a->y + coords[i].y;
617
618               if (x < 0) x = arr_width - 1;
619               else if (x >= arr_width) x = 0;
620
621               if (y < 0) y = arr_height - 1;
622               else if (y >= arr_height) y = 0;
623
624               newcell (&arr[y * arr_width + x], a->col, a->speed);
625             }
626
627         if (a->growth >= diaglim) 
628             killcell (a);
629     }
630     
631     randblip ((head->next) == tail);
632     
633     for (a = head->next; a != tail; a = a->next)
634     {
635         if (a->isnext) 
636         {
637             a->isnext = 0;
638             a->speed = a->nextspeed;
639             a->growth = 0.0;
640             a->col = a->nextcol;
641             drawblock (a->x, a->y, a->col + count);
642         }
643     }
644 }
645
646 \f
647 char *progclass = "Petri";
648
649 char *defaults [] = {
650   ".background:         black",
651   ".foreground:         white",
652   "*delay:              10000",
653   "*count:              8",
654   "*size:               4",
655   "*diaglim:            1.414",
656   "*anychan:            0.0015",
657   "*minorchan:          0.5",
658   "*instantdeathchan:   0.2",
659   "*minlifespan:        500",
660   "*maxlifespan:        1500",
661   "*minlifespeed:       0.04",
662   "*maxlifespeed:       0.13",
663   "*mindeathspeed:      0.42",
664   "*maxdeathspeed:      0.46",
665   "*originalcolors:     false",
666   "*memThrottle:        22M",   /* don't malloc more than this much.
667                                    Scale the pixels up if necessary. */
668     0
669 };
670
671 XrmOptionDescRec options [] = {
672   { "-delay",            ".delay",              XrmoptionSepArg, 0 },
673   { "-size",             ".size",               XrmoptionSepArg, 0 },
674   { "-count",            ".count",              XrmoptionSepArg, 0 },
675   { "-diaglim",          ".diaglim",            XrmoptionSepArg, 0 },
676   { "-anychan",          ".anychan",            XrmoptionSepArg, 0 },
677   { "-minorchan",        ".minorchan",          XrmoptionSepArg, 0 },
678   { "-instantdeathchan", ".instantdeathchan",   XrmoptionSepArg, 0 },
679   { "-minlifespan",      ".minlifespan",        XrmoptionSepArg, 0 },
680   { "-maxlifespan",      ".maxlifespan",        XrmoptionSepArg, 0 },
681   { "-minlifespeed",     ".minlifespeed",       XrmoptionSepArg, 0 },
682   { "-maxlifespeed",     ".maxlifespeed",       XrmoptionSepArg, 0 },
683   { "-mindeathspeed",    ".mindeathspeed",      XrmoptionSepArg, 0 },
684   { "-maxdeathspeed",    ".maxdeathspeed",      XrmoptionSepArg, 0 },
685   { "-originalcolors",   ".originalcolors",     XrmoptionNoArg,  "true" },
686   { "-mem-throttle",     ".memThrottle",        XrmoptionSepArg,  0 },
687   { 0, 0, 0, 0 }
688 };
689
690 void screenhack (Display *dpy, Window win)
691 {
692     int delay = get_integer_resource ("delay", "Delay");
693     display = dpy;
694     window = win;
695     setup_display ();
696     
697     setup_arr ();
698     
699     randblip (1);
700     
701     for (;;) 
702     {
703         update ();
704         XSync (dpy, False);
705         screenhack_handle_events (dpy);
706         usleep (delay);
707     }
708 }