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