5669304b374df58b38c1bb18d146c3f88d4b5f45
[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->dpy, 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->dpy, st->wattr.visual,
227                             st->wattr.colormap,
228                             *colors, n_colors,
229                             True, &writable, True);
230     }
231   else 
232     {
233       make_uniform_colormap (st->dpy, 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->dpy, 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->wattr.height / st->settings.cell_size;
293
294   /* Construct the initial model state. */
295
296   st->settings.trail = clip(2, get_integer_resource (st->dpy, "trail", "Integer"), 1000);
297   
298   st->history = calloc (st->settings.trail, sizeof (st->history[0]));
299   if (!st->history)
300     {
301       fprintf (stderr, "critical: "
302                "couldn't allocate trail history of %d cells\n",
303                st->settings.trail);
304       abort();
305     }
306
307   st->model = model_allocate (model_w, model_h);
308   if (!st->model)
309     {
310       fprintf (stderr, "critical: error preparing the model\n");
311       abort();
312     }
313   
314   /* make a black gc for the background */
315   st->gcv.foreground = get_pixel_resource (st->dpy, st->wattr.colormap,
316                                        "background", "Background");
317   st->bgc = XCreateGC (st->dpy, st->window, GCForeground, &st->gcv);
318
319   st->fgc = XCreateGC (st->dpy, st->window, 0, &st->gcv);
320
321 #ifdef HAVE_COCOA
322   jwxyz_XSetAntiAliasing (dpy, st->fgc, False);
323   jwxyz_XSetAntiAliasing (dpy, st->bgc, False);
324 #endif
325
326   st->delay_usecs = get_integer_resource (st->dpy, "delay", "Integer");
327   st->n_restart = get_integer_resource (st->dpy, "restart", "Integer");
328     
329   setup_colormap (st, &st->d_colors, &st->d_n_colors);
330   model_initialize (st->model);
331   model_step (st->model, &st->history[0]);
332   st->d_pos = 1;
333   st->d_wrapped = 0;
334   st->i_restart = 0;
335   st->d_i_batch = st->batchcount;
336
337   return st;
338 }
339
340 static unsigned long
341 critical_draw (Display *dpy, Window window, void *closure)
342 {
343   struct state *st = (struct state *) closure;
344
345   if (st->eraser) {
346     st->eraser = erase_window (st->dpy, st->window, st->eraser);
347     return st->delay_usecs;
348   }
349
350   /* for (d_i_batch = batchcount; d_i_batch; d_i_batch--) */
351     {
352       /* Set color */
353       if ((st->d_i_batch % st->lines_per_color) == 0)
354         {
355           st->d_i_color = (st->d_i_color + 1) % st->d_n_colors;
356           st->gcv.foreground = st->d_colors[st->d_i_color].pixel;
357           XChangeGC (st->dpy, st->fgc, GCForeground, &st->gcv);
358         }
359         
360       assert(st->d_pos >= 0 && st->d_pos < st->settings.trail);
361       model_step (st->model, &st->history[st->d_pos]);
362
363       draw_step (st, st->fgc, st->d_pos);
364
365       /* we use the history as a ring buffer, but don't start erasing until
366          we've d_wrapped around once. */
367       if (++st->d_pos >= st->settings.trail)
368         {
369           st->d_pos -= st->settings.trail;
370           st->d_wrapped = 1;
371         }
372
373       if (st->d_wrapped)
374         {
375           draw_step (st, st->bgc, st->d_pos+1);
376         }
377
378     }
379     
380   st->d_i_batch--;
381   if (st->d_i_batch < 0)
382     st->d_i_batch = st->batchcount;
383   else
384     return st->delay_usecs;
385
386   st->i_restart = (st->i_restart + 1) % st->n_restart;
387
388   if (st->i_restart == 0)
389     {
390       /* Time to start a new simulation, this one has probably got
391          to be a bit boring. */
392       free_colormap (st, &st->d_colors, st->d_n_colors);
393       setup_colormap (st, &st->d_colors, &st->d_n_colors);
394       st->eraser = erase_window (st->dpy, st->window, st->eraser);
395       model_initialize (st->model);
396       model_step (st->model, &st->history[0]);
397       st->d_pos = 1;
398       st->d_wrapped = 0;
399       st->d_i_batch = st->batchcount;
400     }
401
402   return st->delay_usecs;
403 }
404
405 static void
406 critical_reshape (Display *dpy, Window window, void *closure, 
407                  unsigned int w, unsigned int h)
408 {
409 }
410
411 static Bool
412 critical_event (Display *dpy, Window window, void *closure, XEvent *event)
413 {
414   return False;
415 }
416
417 static void
418 critical_free (Display *dpy, Window window, void *closure)
419 {
420   struct state *st = (struct state *) closure;
421   free (st);
422 }
423
424
425 /* Options this module understands.  */
426 static XrmOptionDescRec critical_options[] = {
427   { "-ncolors",         ".ncolors",     XrmoptionSepArg, 0 },
428   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
429   { "-colorscheme",     ".colorscheme", XrmoptionSepArg, 0 },
430   { "-restart",         ".restart",     XrmoptionSepArg, 0 },
431   { "-batchcount",      ".batchcount",  XrmoptionSepArg, 0 },
432   { "-trail",           ".trail",       XrmoptionSepArg, 0 },
433   { 0, 0, 0, 0 }                /* end */
434 };
435
436
437 /* Default xrm resources. */
438 static const char *critical_defaults[] = {
439   ".background:                 black",
440   "*colorscheme:                smooth",
441   "*delay:                      10000", 
442   "*ncolors:                    64",
443   "*restart:                    8",
444   "*batchcount:                 1500",
445   "*trail:                      50",
446   0                             /* end */
447 };
448
449
450 XSCREENSAVER_MODULE ("Critical", critical)