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