1e8f3a1ab19731ca7283f51a35402ef7647581a7
[xscreensaver] / hacks / critical.c
1 /* critical -- Self-organizing-criticality display hack for XScreenSaver
2  * Copyright (C) 1998, 1999, 2000 Martin Pool <mbp@humbug.org.au>
3  *
4  * Permission to use, copy, modify, distribute, and sell this software
5  * and its documentation for any purpose is hereby granted without
6  * fee, provided that the above copyright notice appear in all copies
7  * and that both that copyright notice and this permission notice
8  * appear in supporting documentation.  No representations are made
9  * about the suitability of this software for any purpose.  It is
10  * provided "as is" without express or implied warranty.
11  *
12  * See `critical.man' for more information.
13  *
14  * Revision history:
15  * 13 Nov 1998: Initial version, Martin Pool <mbp@humbug.org.au>
16  * 08 Feb 2000: Change to keeping and erasing a trail, <mbp>
17  *
18  * It would be nice to draw curvy shapes rather than just straight
19  * lines, but X11 doesn't have spline primitives (?) so we'd have to
20  * do all the work ourselves  */
21
22 #include "screenhack.h"
23 #include "erase.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <assert.h>
28
29 char *progclass = "Critical";
30
31
32 typedef struct {
33   int width, height;            /* in cells */
34   unsigned short *cells;
35 } CriticalModel;
36
37 typedef struct {
38   int trail;                    /* length of trail */
39   int cell_size;
40 } CriticalSettings;
41
42
43 CriticalModel * model_allocate (int w, int h);
44 void model_initialize (CriticalModel *model);
45
46 /* Options this module understands.  */
47 XrmOptionDescRec options[] = {
48   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
49   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
50   { "-colorscheme",     ".colorscheme", XrmoptionSepArg, 0 },
51   { "-restart",         ".restart",     XrmoptionSepArg, 0 },
52   { "-cellsize",        ".cellsize",    XrmoptionSepArg, 0 },
53   { "-batchcount",      ".batchcount",  XrmoptionSepArg, 0 },
54   { "-trail",           ".trail",       XrmoptionSepArg, 0 },
55   { 0, 0, 0, 0 }                /* end */
56 };
57
58
59 /* Default xrm resources. */
60 char *defaults[] = {
61   ".background:                 black",
62   "*colorscheme:                smooth",
63   "*delay:                      10000", 
64   "*ncolors:                    64",
65   "*restart:                    8",
66   "*batchcount:                 1500",
67   "*trail:                      50",
68   0                             /* end */
69 };
70
71
72 int
73 clip (int low, int val, int high)
74 {
75   if (val < low)
76     return low;
77   else if (val > high)
78     return high;
79   else
80     return val;
81 }
82
83
84 /* Allocate an return a new simulation model datastructure.
85  */
86
87 CriticalModel *
88 model_allocate (int model_w, int model_h)
89 {
90   CriticalModel         *model;
91
92   model = malloc (sizeof (CriticalModel));
93   if (!model)
94     return 0;
95
96   model->width = model_w;
97   model->height = model_h;
98
99   model->cells = malloc (sizeof (int) * model_w * model_h);
100   if (!model->cells)
101     return 0;
102
103   return model;
104 }
105
106
107
108 /* Initialize the data model underlying the hack.
109
110    For the self-organizing criticality hack, this consists of a 2d
111    array full of random integers.
112
113    I've considered storing the data as (say) a binary tree within a 2d
114    array, to make finding the highest value faster at the expense of
115    storage space: searching the whole array on each iteration seems a
116    little inefficient.  However, the screensaver doesn't seem to take
117    up many cycles as it is: presumably the search is pretty quick
118    compared to the sleeps.  The current version uses less than 1% of
119    the CPU time of an AMD K6-233.  Many machines running X11 at this
120    point in time seem to be memory-limited, not CPU-limited.
121
122    The root of all evil, and all that.
123 */
124
125
126 void
127 model_initialize (CriticalModel *model)
128 {
129   int i;
130   
131   for (i = model->width * model->height; i >= 0; i--)
132     {
133       model->cells[i] = (unsigned short) random ();
134     }
135 }
136
137
138 /* Move one step forward in the criticality simulation.
139
140    This function locates and returns in (TOP_X, TOP_Y) the location of
141    the highest-valued cell in the model.  It also replaces that cell
142    and it's eight nearest neighbours with new random values.
143    Neighbours that fall off the edge of the model are simply
144    ignored. */
145 static void
146 model_step (CriticalModel *model, XPoint *ptop)
147 {
148   int                   x, y, i;
149   int                   dx, dy;
150   unsigned short        top_value = 0;
151   int                   top_x = 0, top_y = 0;
152
153   /* Find the top cell */
154   top_value = 0;
155   i = 0;
156   for (y = 0; y < model->height; y++)
157     for (x = 0; x < model->width; x++)
158       {
159         if (model->cells[i] >= top_value)
160           {
161             top_value = model->cells[i];
162             top_x = x;
163             top_y = y;
164           }
165         i++;
166       }
167
168   /* Replace it and its neighbours with new random values */
169   for (dy = -1; dy <= 1; dy++)
170     {
171       int y = top_y + dy;
172       if (y < 0  ||  y >= model->height)
173         continue;
174       
175       for (dx = -1; dx <= 1; dx++)
176         {
177           int x = top_x + dx;
178           if (x < 0  ||  x >= model->width)
179             continue;
180           
181           model->cells[y * model->width + x] = (unsigned short) random();
182         }
183     }
184
185   ptop->x = top_x;
186   ptop->y = top_y;
187 }
188
189
190 /* Construct and return in COLORS and N_COLORS a new set of colors,
191    depending on the resource settings.  */
192 void
193 setup_colormap (Display *dpy, XWindowAttributes *wattr,
194                 XColor **colors,
195                 int *n_colors)
196 {
197   Bool                  writable;
198   char const *          color_scheme;
199
200   /* Make a colormap */
201   *n_colors = get_integer_resource ("ncolors", "Integer");
202   if (*n_colors < 2)
203     *n_colors = 2;
204   
205   *colors = (XColor *) calloc (sizeof(XColor), *n_colors);
206   if (!*colors)
207     {
208       fprintf (stderr, "%s:%d: can't allocate memory for colors\n",
209                __FILE__, __LINE__);
210       return;
211     }
212
213   writable = False;
214   color_scheme = get_string_resource ("colorscheme", "ColorScheme");
215   
216   if (!strcmp (color_scheme, "random"))
217     {
218       make_random_colormap (dpy, wattr->visual,
219                             wattr->colormap,
220                             *colors, n_colors,
221                             True, True, &writable, True);
222     }
223   else if (!strcmp (color_scheme, "smooth"))
224     {
225       make_smooth_colormap (dpy, wattr->visual,
226                             wattr->colormap,
227                             *colors, n_colors,
228                             True, &writable, True);
229     }
230   else 
231     {
232       make_uniform_colormap (dpy, wattr->visual,
233                              wattr->colormap,
234                              *colors, n_colors, True,
235                              &writable, True);
236     }
237 }
238
239
240 /* Draw one step of the hack.  Positions are cell coordinates. */
241 static void
242 draw_step (CriticalSettings *settings,
243            Display *dpy, Window window, GC gc,
244            int pos, XPoint *history)
245 {
246   int cell_size = settings->cell_size;
247   int half = cell_size/2;
248   int old_pos = (pos + settings->trail - 1) % settings->trail;
249   
250   pos = pos % settings->trail;
251
252   XDrawLine (dpy, window, gc, 
253              history[pos].x * cell_size + half,
254              history[pos].y * cell_size + half,
255              history[old_pos].x * cell_size + half,
256              history[old_pos].y * cell_size + half);
257 }
258
259
260
261 /* Display a self-organizing criticality screen hack.  The program
262    runs indefinately on the root window. */
263 void
264 screenhack (Display *dpy, Window window)
265 {
266   int                   n_colors;
267   XColor                *colors;
268   int                   model_w, model_h;
269   CriticalModel         *model;
270   int                   lines_per_color = 10;
271   int                   i_color = 0;
272   long                  delay_usecs;
273   int                   batchcount;
274   XPoint                *history; /* in cell coords */
275   int                   pos = 0;
276   int                   wrapped = 0;
277   GC                    fgc, bgc;
278   XGCValues             gcv;
279   XWindowAttributes     wattr;
280   CriticalSettings      settings;
281
282   /* Number of screens that should be drawn before reinitializing the
283      model, and count of the number of screens done so far. */
284   int                   n_restart, i_restart;
285
286   /* Find window attributes */
287   XGetWindowAttributes (dpy, window, &wattr);
288
289   batchcount = get_integer_resource ("batchcount", "Integer");
290   if (batchcount < 5)
291     batchcount = 5;
292
293   /* For the moment the model size is just fixed -- making it vary
294      with the screen size just makes the hack boring on large
295      screens. */
296   model_w = 80;
297   settings.cell_size = wattr.width / model_w;
298   model_h = wattr.height / settings.cell_size;
299
300   /* Construct the initial model state. */
301
302   settings.trail = clip(2, get_integer_resource ("trail", "Integer"), 1000);
303   
304   history = malloc (sizeof history[0] * settings.trail);
305   if (!history)
306     {
307       fprintf (stderr, "critical: "
308                "couldn't allocate trail history of %d cells\n",
309                settings.trail);
310       return;
311     }
312
313   model = model_allocate (model_w, model_h);
314   if (!model)
315     {
316       fprintf (stderr, "critical: error preparing the model\n");
317       return;
318     }
319   
320   /* make a black gc for the background */
321   gcv.foreground = get_pixel_resource ("background", "Background",
322                                        dpy, wattr.colormap);
323   bgc = XCreateGC (dpy, window, GCForeground, &gcv);
324
325   fgc = XCreateGC (dpy, window, 0, &gcv);
326
327   delay_usecs = get_integer_resource ("delay", "Integer");
328   n_restart = get_integer_resource ("restart", "Integer");
329     
330   /* xscreensaver will kill or stop us when the user does something
331    * that deserves attention. */
332   i_restart = 0;
333   
334   while (1) {
335     int i_batch;
336
337     if (i_restart == 0)
338       {
339         /* Time to start a new simulation, this one has probably got
340            to be a bit boring. */
341         setup_colormap (dpy, &wattr, &colors, &n_colors);
342         erase_full_window (dpy, window);
343         model_initialize (model);
344         pos = 1;
345         wrapped = 0;
346       }
347     
348     for (i_batch = batchcount; i_batch; i_batch--)
349       {
350         /* Set color */
351         if ((i_batch % lines_per_color) == 0)
352           {
353             i_color = (i_color + 1) % n_colors;
354             gcv.foreground = colors[i_color].pixel;
355             XChangeGC (dpy, fgc, GCForeground, &gcv);
356           }
357         
358         assert(pos >= 0 && pos < settings.trail);
359         model_step (model, &history[pos]);
360
361         draw_step (&settings, dpy, window, fgc, pos, history);
362
363         /* we use the history as a ring buffer, but don't start erasing until
364            we've wrapped around once. */
365         if (++pos >= settings.trail)
366           {
367             pos -= settings.trail;
368             wrapped = 1;
369           }
370
371         if (wrapped)
372           {
373             draw_step (&settings, dpy, window, bgc, pos+1, history);
374           }
375
376         XSync (dpy, False); 
377         screenhack_handle_events (dpy);
378         
379         if (delay_usecs)
380           usleep (delay_usecs);
381
382       }
383     
384     i_restart = (i_restart + 1) % n_restart;
385   }
386 }
387
388
389 /*
390  * Local variables:
391  * c-indent-mode: gnu
392  * compile-command "make critical && ./critical"
393  * End:
394  */