From http://www.jwz.org/xscreensaver/xscreensaver-5.35.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
30 typedef struct {
31   int width, height;            /* in cells */
32   unsigned short *cells;
33 } CriticalModel;
34
35 typedef struct {
36   int trail;                    /* length of trail */
37   int cell_size;
38 } CriticalSettings;
39
40
41 /* Number of screens that should be drawn before reinitializing the
42    model, and count of the number of screens done so far. */
43
44 struct state {
45   Display *dpy;
46   Window window;
47
48   int n_restart, i_restart;
49   XWindowAttributes     wattr;
50   CriticalModel         *model;
51   int                   batchcount;
52   XPoint                *history; /* in cell coords */
53   long                  delay_usecs;
54   GC                    fgc, bgc;
55   XGCValues             gcv;
56   CriticalSettings      settings;
57
58   int                   d_n_colors;
59   XColor                        *d_colors;
60   int                   lines_per_color;
61   int                   d_i_color;
62   int                   d_pos;
63   int                   d_wrapped;
64
65   int d_i_batch;
66   eraser_state *eraser;
67
68 };
69
70
71 static CriticalModel * model_allocate (int w, int h);
72 static void model_initialize (CriticalModel *);
73
74
75 static int
76 clip (int low, int val, int high)
77 {
78   if (val < low)
79     return low;
80   else if (val > high)
81     return high;
82   else
83     return val;
84 }
85
86
87 /* Allocate an return a new simulation model datastructure.
88  */
89
90 static CriticalModel *
91 model_allocate (int model_w, int model_h)
92 {
93   CriticalModel         *mm;
94
95   mm = malloc (sizeof (CriticalModel));
96   if (!mm)
97     return 0;
98
99   mm->width = model_w;
100   mm->height = model_h;
101
102   mm->cells = malloc (sizeof (unsigned short) * model_w * model_h);
103   if (!mm->cells) {
104     free (mm);
105     return 0;
106   }
107
108   return mm;
109 }
110
111
112
113 /* Initialize the data model underlying the hack.
114
115    For the self-organizing criticality hack, this consists of a 2d
116    array full of random integers.
117
118    I've considered storing the data as (say) a binary tree within a 2d
119    array, to make finding the highest value faster at the expense of
120    storage space: searching the whole array on each iteration seems a
121    little inefficient.  However, the screensaver doesn't seem to take
122    up many cycles as it is: presumably the search is pretty quick
123    compared to the sleeps.  The current version uses less than 1% of
124    the CPU time of an AMD K6-233.  Many machines running X11 at this
125    point in time seem to be memory-limited, not CPU-limited.
126
127    The root of all evil, and all that.
128 */
129
130
131 static void
132 model_initialize (CriticalModel *mm)
133 {
134   int i;
135   
136   for (i = mm->width * mm->height - 1; i >= 0; i--)
137     {
138       mm->cells[i] = (unsigned short) random ();
139     }
140 }
141
142
143 /* Move one step forward in the criticality simulation.
144
145    This function locates and returns in (TOP_X, TOP_Y) the location of
146    the highest-valued cell in the model.  It also replaces that cell
147    and it's eight nearest neighbours with new random values.
148    Neighbours that fall off the edge of the model are simply
149    ignored. */
150 static void
151 model_step (CriticalModel *mm, XPoint *ptop)
152 {
153   int                   x, y, i;
154   int                   dx, dy;
155   unsigned short        top_value = 0;
156   int                   top_x = 0, top_y = 0;
157
158   /* Find the top cell */
159   top_value = 0;
160   i = 0;
161   for (y = 0; y < mm->height; y++)
162     for (x = 0; x < mm->width; x++)
163       {
164         if (mm->cells[i] >= top_value)
165           {
166             top_value = mm->cells[i];
167             top_x = x;
168             top_y = y;
169           }
170         i++;
171       }
172
173   /* Replace it and its neighbours with new random values */
174   for (dy = -1; dy <= 1; dy++)
175     {
176       int yy = top_y + dy;
177       if (yy < 0  ||  yy >= mm->height)
178         continue;
179       
180       for (dx = -1; dx <= 1; dx++)
181         {
182           int xx = top_x + dx;
183           if (xx < 0  ||  xx >= mm->width)
184             continue;
185           
186           mm->cells[yy * mm->width + xx] = (unsigned short) random();
187         }
188     }
189
190   ptop->x = top_x;
191   ptop->y = top_y;
192 }
193
194
195 /* Construct and return in COLORS and N_COLORS a new set of colors,
196    depending on the resource settings.  */
197 static void
198 setup_colormap (struct state *st, XColor **colors, int *n_colors)
199 {
200   Bool                  writable;
201   char const *          color_scheme;
202
203   /* Make a colormap */
204   *n_colors = get_integer_resource (st->dpy, "ncolors", "Integer");
205   if (*n_colors < 3)
206     *n_colors = 3;
207   
208   *colors = (XColor *) calloc (sizeof(XColor), *n_colors);
209   if (!*colors)
210     {
211       fprintf (stderr, "%s:%d: can't allocate memory for colors\n",
212                __FILE__, __LINE__);
213       return;
214     }
215
216   writable = False;
217   color_scheme = get_string_resource (st->dpy, "colorscheme", "ColorScheme");
218   
219   if (!strcmp (color_scheme, "random"))
220     {
221       make_random_colormap (st->wattr.screen, st->wattr.visual,
222                             st->wattr.colormap,
223                             *colors, n_colors,
224                             True, True, &writable, True);
225     }
226   else if (!strcmp (color_scheme, "smooth"))
227     {
228       make_smooth_colormap (st->wattr.screen, st->wattr.visual,
229                             st->wattr.colormap,
230                             *colors, n_colors,
231                             True, &writable, True);
232     }
233   else 
234     {
235       make_uniform_colormap (st->wattr.screen, st->wattr.visual,
236                              st->wattr.colormap,
237                              *colors, n_colors, True,
238                              &writable, True);
239     }
240 }
241
242
243 /* Free allocated colormap created by setup_colormap. */
244 static void
245 free_colormap (struct state *st, XColor **colors, int n_colors)
246 {
247   free_colors (st->wattr.screen, st->wattr.colormap, *colors, n_colors);
248   free (*colors);
249 }
250
251
252
253 /* Draw one step of the hack.  Positions are cell coordinates. */
254 static void
255 draw_step (struct state *st, GC gc, int pos)
256 {
257   int cell_size = st->settings.cell_size;
258   int half = cell_size/2;
259   int old_pos = (pos + st->settings.trail - 1) % st->settings.trail;
260   
261   pos = pos % st->settings.trail;
262
263   XDrawLine (st->dpy, st->window, gc, 
264              st->history[pos].x * cell_size + half,
265              st->history[pos].y * cell_size + half,
266              st->history[old_pos].x * cell_size + half,
267              st->history[old_pos].y * cell_size + half);
268 }
269
270
271
272 static void *
273 critical_init (Display *dpy, Window window)
274 {
275   struct state *st = (struct state *) calloc (1, sizeof(*st));
276   int                   model_w, model_h;
277   st->dpy = dpy;
278   st->window = window;
279
280   /* Find window attributes */
281   XGetWindowAttributes (st->dpy, st->window, &st->wattr);
282
283   st->batchcount = get_integer_resource (st->dpy, "batchcount", "Integer");
284   if (st->batchcount < 5)
285     st->batchcount = 5;
286
287   st->lines_per_color = 10;
288
289   /* For the moment the model size is just fixed -- making it vary
290      with the screen size just makes the hack boring on large
291      screens. */
292   model_w = 80;
293   st->settings.cell_size = st->wattr.width / model_w;
294   model_h = st->settings.cell_size ?
295     st->wattr.height / st->settings.cell_size : 1;
296
297   /* Construct the initial model state. */
298
299   st->settings.trail = clip(2, get_integer_resource (st->dpy, "trail", "Integer"), 1000);
300   
301   st->history = calloc (st->settings.trail, sizeof (st->history[0]));
302   if (!st->history)
303     {
304       fprintf (stderr, "critical: "
305                "couldn't allocate trail history of %d cells\n",
306                st->settings.trail);
307       abort();
308     }
309
310   st->model = model_allocate (model_w, model_h);
311   if (!st->model)
312     {
313       fprintf (stderr, "critical: error preparing the model\n");
314       abort();
315     }
316   
317   /* make a black gc for the background */
318   st->gcv.foreground = get_pixel_resource (st->dpy, st->wattr.colormap,
319                                        "background", "Background");
320   st->bgc = XCreateGC (st->dpy, st->window, GCForeground, &st->gcv);
321
322   st->fgc = XCreateGC (st->dpy, st->window, 0, &st->gcv);
323
324 #ifdef HAVE_JWXYZ
325   jwxyz_XSetAntiAliasing (dpy, st->fgc, False);
326   jwxyz_XSetAntiAliasing (dpy, st->bgc, False);
327 #endif
328
329   st->delay_usecs = get_integer_resource (st->dpy, "delay", "Integer");
330   st->n_restart = get_integer_resource (st->dpy, "restart", "Integer");
331     
332   setup_colormap (st, &st->d_colors, &st->d_n_colors);
333   model_initialize (st->model);
334   model_step (st->model, &st->history[0]);
335   st->d_pos = 1;
336   st->d_wrapped = 0;
337   st->i_restart = 0;
338   st->d_i_batch = st->batchcount;
339
340   return st;
341 }
342
343 static unsigned long
344 critical_draw (Display *dpy, Window window, void *closure)
345 {
346   struct state *st = (struct state *) closure;
347
348   if (st->eraser) {
349     st->eraser = erase_window (st->dpy, st->window, st->eraser);
350     return st->delay_usecs;
351   }
352
353   /* for (d_i_batch = batchcount; d_i_batch; d_i_batch--) */
354     {
355       /* Set color */
356       if ((st->d_i_batch % st->lines_per_color) == 0)
357         {
358           st->d_i_color = (st->d_i_color + 1) % st->d_n_colors;
359           st->gcv.foreground = st->d_colors[st->d_i_color].pixel;
360           XChangeGC (st->dpy, st->fgc, GCForeground, &st->gcv);
361         }
362         
363       assert(st->d_pos >= 0 && st->d_pos < st->settings.trail);
364       model_step (st->model, &st->history[st->d_pos]);
365
366       draw_step (st, st->fgc, st->d_pos);
367
368       /* we use the history as a ring buffer, but don't start erasing until
369          we've d_wrapped around once. */
370       if (++st->d_pos >= st->settings.trail)
371         {
372           st->d_pos -= st->settings.trail;
373           st->d_wrapped = 1;
374         }
375
376       if (st->d_wrapped)
377         {
378           draw_step (st, st->bgc, st->d_pos+1);
379         }
380
381     }
382     
383   st->d_i_batch--;
384   if (st->d_i_batch < 0)
385     st->d_i_batch = st->batchcount;
386   else
387     return st->delay_usecs;
388
389   st->i_restart = (st->i_restart + 1) % st->n_restart;
390
391   if (st->i_restart == 0)
392     {
393       /* Time to start a new simulation, this one has probably got
394          to be a bit boring. */
395       free_colormap (st, &st->d_colors, st->d_n_colors);
396       setup_colormap (st, &st->d_colors, &st->d_n_colors);
397       st->eraser = erase_window (st->dpy, st->window, st->eraser);
398       model_initialize (st->model);
399       model_step (st->model, &st->history[0]);
400       st->d_pos = 1;
401       st->d_wrapped = 0;
402       st->d_i_batch = st->batchcount;
403     }
404
405   return st->delay_usecs;
406 }
407
408 static void
409 critical_reshape (Display *dpy, Window window, void *closure, 
410                  unsigned int w, unsigned int h)
411 {
412 }
413
414 static Bool
415 critical_event (Display *dpy, Window window, void *closure, XEvent *event)
416 {
417   return False;
418 }
419
420 static void
421 critical_free (Display *dpy, Window window, void *closure)
422 {
423   struct state *st = (struct state *) closure;
424   free (st);
425 }
426
427
428 /* Options this module understands.  */
429 static XrmOptionDescRec critical_options[] = {
430   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
431   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
432   { "-colorscheme",     ".colorscheme", XrmoptionSepArg, 0 },
433   { "-restart",         ".restart",     XrmoptionSepArg, 0 },
434   { "-batchcount",      ".batchcount",  XrmoptionSepArg, 0 },
435   { "-trail",           ".trail",       XrmoptionSepArg, 0 },
436   { 0, 0, 0, 0 }                /* end */
437 };
438
439
440 /* Default xrm resources. */
441 static const char *critical_defaults[] = {
442   ".background:                 black",
443   "*fpsSolid:                   true",
444   "*colorscheme:                smooth",
445   "*delay:                      10000", 
446   "*ncolors:                    64",
447   "*restart:                    8",
448   "*batchcount:                 1500",
449   "*trail:                      50",
450   0                             /* end */
451 };
452
453
454 XSCREENSAVER_MODULE ("Critical", critical)