ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-4.06.tar.gz
[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 (unsigned short) * 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 - 1; 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 < 3)
203     *n_colors = 3;
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 /* Free allocated colormap created by setup_colormap. */
241 static void
242 free_colormap (Display *dpy, XWindowAttributes *wattr,
243                XColor **colors, int n_colors)
244 {
245   free_colors (dpy, wattr->colormap, *colors, n_colors);
246   free (*colors);
247 }
248
249
250
251 /* Draw one step of the hack.  Positions are cell coordinates. */
252 static void
253 draw_step (CriticalSettings *settings,
254            Display *dpy, Window window, GC gc,
255            int pos, XPoint *history)
256 {
257   int cell_size = settings->cell_size;
258   int half = cell_size/2;
259   int old_pos = (pos + settings->trail - 1) % settings->trail;
260   
261   pos = pos % settings->trail;
262
263   XDrawLine (dpy, window, gc, 
264              history[pos].x * cell_size + half,
265              history[pos].y * cell_size + half,
266              history[old_pos].x * cell_size + half,
267              history[old_pos].y * cell_size + half);
268 }
269
270
271
272 /* Display a self-organizing criticality screen hack.  The program
273    runs indefinately on the root window. */
274 void
275 screenhack (Display *dpy, Window window)
276 {
277   int                   n_colors;
278   XColor                *colors;
279   int                   model_w, model_h;
280   CriticalModel         *model;
281   int                   lines_per_color = 10;
282   int                   i_color = 0;
283   long                  delay_usecs;
284   int                   batchcount;
285   XPoint                *history; /* in cell coords */
286   int                   pos = 0;
287   int                   wrapped = 0;
288   GC                    fgc, bgc;
289   XGCValues             gcv;
290   XWindowAttributes     wattr;
291   CriticalSettings      settings;
292
293   /* Number of screens that should be drawn before reinitializing the
294      model, and count of the number of screens done so far. */
295   int                   n_restart, i_restart;
296
297   /* Find window attributes */
298   XGetWindowAttributes (dpy, window, &wattr);
299
300   batchcount = get_integer_resource ("batchcount", "Integer");
301   if (batchcount < 5)
302     batchcount = 5;
303
304   /* For the moment the model size is just fixed -- making it vary
305      with the screen size just makes the hack boring on large
306      screens. */
307   model_w = 80;
308   settings.cell_size = wattr.width / model_w;
309   model_h = wattr.height / settings.cell_size;
310
311   /* Construct the initial model state. */
312
313   settings.trail = clip(2, get_integer_resource ("trail", "Integer"), 1000);
314   
315   history = malloc (sizeof history[0] * settings.trail);
316   if (!history)
317     {
318       fprintf (stderr, "critical: "
319                "couldn't allocate trail history of %d cells\n",
320                settings.trail);
321       return;
322     }
323
324   model = model_allocate (model_w, model_h);
325   if (!model)
326     {
327       fprintf (stderr, "critical: error preparing the model\n");
328       return;
329     }
330   
331   /* make a black gc for the background */
332   gcv.foreground = get_pixel_resource ("background", "Background",
333                                        dpy, wattr.colormap);
334   bgc = XCreateGC (dpy, window, GCForeground, &gcv);
335
336   fgc = XCreateGC (dpy, window, 0, &gcv);
337
338   delay_usecs = get_integer_resource ("delay", "Integer");
339   n_restart = get_integer_resource ("restart", "Integer");
340     
341   /* xscreensaver will kill or stop us when the user does something
342    * that deserves attention. */
343   i_restart = 0;
344   
345   while (1) {
346     int i_batch;
347
348     if (i_restart == 0)
349       {
350         /* Time to start a new simulation, this one has probably got
351            to be a bit boring. */
352         setup_colormap (dpy, &wattr, &colors, &n_colors);
353         erase_full_window (dpy, window);
354         model_initialize (model);
355         model_step (model, &history[0]);
356         pos = 1;
357         wrapped = 0;
358       }
359     
360     for (i_batch = batchcount; i_batch; i_batch--)
361       {
362         /* Set color */
363         if ((i_batch % lines_per_color) == 0)
364           {
365             i_color = (i_color + 1) % n_colors;
366             gcv.foreground = colors[i_color].pixel;
367             XChangeGC (dpy, fgc, GCForeground, &gcv);
368           }
369         
370         assert(pos >= 0 && pos < settings.trail);
371         model_step (model, &history[pos]);
372
373         draw_step (&settings, dpy, window, fgc, pos, history);
374
375         /* we use the history as a ring buffer, but don't start erasing until
376            we've wrapped around once. */
377         if (++pos >= settings.trail)
378           {
379             pos -= settings.trail;
380             wrapped = 1;
381           }
382
383         if (wrapped)
384           {
385             draw_step (&settings, dpy, window, bgc, pos+1, history);
386           }
387
388         XSync (dpy, False); 
389         screenhack_handle_events (dpy);
390         
391         if (delay_usecs)
392           usleep (delay_usecs);
393
394       }
395     
396     i_restart = (i_restart + 1) % n_restart;
397
398     if (i_restart == 0)
399       {
400         /* Clean up after completing a simulation. */
401         free_colormap (dpy, &wattr, &colors, n_colors);
402       }
403   }
404 }