http://ftp.x.org/contrib/applications/xscreensaver-3.24.tar.gz
[xscreensaver] / hacks / critical.c
index 3f8f2763ab85dc812ca6fc16dd49fcc068fcd8e8..1e8f3a1ab19731ca7283f51a35402ef7647581a7 100644 (file)
@@ -1,5 +1,5 @@
 /* critical -- Self-organizing-criticality display hack for XScreenSaver
- * Copyright (C) 1998, 1999 Martin Pool <mbp@humbug.org.au>
+ * Copyright (C) 1998, 1999, 2000 Martin Pool <mbp@humbug.org.au>
  *
  * Permission to use, copy, modify, distribute, and sell this software
  * and its documentation for any purpose is hereby granted without
  *
  * Revision history:
  * 13 Nov 1998: Initial version, Martin Pool <mbp@humbug.org.au>
- */
+ * 08 Feb 2000: Change to keeping and erasing a trail, <mbp>
+ *
+ * It would be nice to draw curvy shapes rather than just straight
+ * lines, but X11 doesn't have spline primitives (?) so we'd have to
+ * do all the work ourselves  */
 
 #include "screenhack.h"
 #include "erase.h"
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <assert.h>
 
 char *progclass = "Critical";
 
@@ -29,11 +34,14 @@ typedef struct {
   unsigned short *cells;
 } CriticalModel;
 
+typedef struct {
+  int trail;                   /* length of trail */
+  int cell_size;
+} CriticalSettings;
+
 
 CriticalModel * model_allocate (int w, int h);
 void model_initialize (CriticalModel *model);
-static void model_step (CriticalModel *model, int *top_x, int *top_y);
-
 
 /* Options this module understands.  */
 XrmOptionDescRec options[] = {
@@ -43,6 +51,7 @@ XrmOptionDescRec options[] = {
   { "-restart",                ".restart",     XrmoptionSepArg, 0 },
   { "-cellsize",       ".cellsize",    XrmoptionSepArg, 0 },
   { "-batchcount",     ".batchcount",  XrmoptionSepArg, 0 },
+  { "-trail",          ".trail",       XrmoptionSepArg, 0 },
   { 0, 0, 0, 0 }               /* end */
 };
 
@@ -54,12 +63,24 @@ char *defaults[] = {
   "*delay:                     10000", 
   "*ncolors:                   64",
   "*restart:                   8",
-  "*cellsize:                  9",
   "*batchcount:                        1500",
+  "*trail:                     50",
   0                            /* end */
 };
 
 
+int
+clip (int low, int val, int high)
+{
+  if (val < low)
+    return low;
+  else if (val > high)
+    return high;
+  else
+    return val;
+}
+
+
 /* Allocate an return a new simulation model datastructure.
  */
 
@@ -122,11 +143,12 @@ model_initialize (CriticalModel *model)
    Neighbours that fall off the edge of the model are simply
    ignored. */
 static void
-model_step (CriticalModel *model, int *top_x, int *top_y)
+model_step (CriticalModel *model, XPoint *ptop)
 {
   int                  x, y, i;
   int                  dx, dy;
-  unsigned short       top_value;
+  unsigned short       top_value = 0;
+  int                  top_x = 0, top_y = 0;
 
   /* Find the top cell */
   top_value = 0;
@@ -137,8 +159,8 @@ model_step (CriticalModel *model, int *top_x, int *top_y)
        if (model->cells[i] >= top_value)
          {
            top_value = model->cells[i];
-           *top_x = x;
-           *top_y = y;
+           top_x = x;
+           top_y = y;
          }
        i++;
       }
@@ -146,19 +168,22 @@ model_step (CriticalModel *model, int *top_x, int *top_y)
   /* Replace it and its neighbours with new random values */
   for (dy = -1; dy <= 1; dy++)
     {
-      int y = *top_y + dy;
+      int y = top_y + dy;
       if (y < 0  ||  y >= model->height)
        continue;
       
       for (dx = -1; dx <= 1; dx++)
        {
-         int x = *top_x + dx;
+         int x = top_x + dx;
          if (x < 0  ||  x >= model->width)
            continue;
          
          model->cells[y * model->width + x] = (unsigned short) random();
        }
     }
+
+  ptop->x = top_x;
+  ptop->y = top_y;
 }
 
 
@@ -212,25 +237,47 @@ setup_colormap (Display *dpy, XWindowAttributes *wattr,
 }
 
 
+/* Draw one step of the hack.  Positions are cell coordinates. */
+static void
+draw_step (CriticalSettings *settings,
+          Display *dpy, Window window, GC gc,
+          int pos, XPoint *history)
+{
+  int cell_size = settings->cell_size;
+  int half = cell_size/2;
+  int old_pos = (pos + settings->trail - 1) % settings->trail;
+  
+  pos = pos % settings->trail;
+
+  XDrawLine (dpy, window, gc, 
+            history[pos].x * cell_size + half,
+            history[pos].y * cell_size + half,
+            history[old_pos].x * cell_size + half,
+            history[old_pos].y * cell_size + half);
+}
+
+
 
 /* Display a self-organizing criticality screen hack.  The program
    runs indefinately on the root window. */
 void
 screenhack (Display *dpy, Window window)
 {
-  GC                   fgc, bgc;
-  XGCValues            gcv;
-  XWindowAttributes    wattr;
   int                  n_colors;
   XColor               *colors;
   int                  model_w, model_h;
   CriticalModel                *model;
   int                  lines_per_color = 10;
   int                  i_color = 0;
-  int                  x1, y1, x2, y2;
   long                 delay_usecs;
-  int                  cell_size;
   int                  batchcount;
+  XPoint               *history; /* in cell coords */
+  int                  pos = 0;
+  int                  wrapped = 0;
+  GC                   fgc, bgc;
+  XGCValues            gcv;
+  XWindowAttributes    wattr;
+  CriticalSettings     settings;
 
   /* Number of screens that should be drawn before reinitializing the
      model, and count of the number of screens done so far. */
@@ -239,20 +286,30 @@ screenhack (Display *dpy, Window window)
   /* Find window attributes */
   XGetWindowAttributes (dpy, window, &wattr);
 
-  /* Construct the initial model state. */
-  cell_size = get_integer_resource ("cellsize", "Integer");
-  if (cell_size < 1)
-    cell_size = 1;
-  if (cell_size >= 100)
-    cell_size = 99;
-
   batchcount = get_integer_resource ("batchcount", "Integer");
   if (batchcount < 5)
     batchcount = 5;
+
+  /* For the moment the model size is just fixed -- making it vary
+     with the screen size just makes the hack boring on large
+     screens. */
+  model_w = 80;
+  settings.cell_size = wattr.width / model_w;
+  model_h = wattr.height / settings.cell_size;
+
+  /* Construct the initial model state. */
+
+  settings.trail = clip(2, get_integer_resource ("trail", "Integer"), 1000);
   
-  model_w = wattr.width / cell_size;
-  model_h = wattr.height  / cell_size;
-  
+  history = malloc (sizeof history[0] * settings.trail);
+  if (!history)
+    {
+      fprintf (stderr, "critical: "
+              "couldn't allocate trail history of %d cells\n",
+              settings.trail);
+      return;
+    }
+
   model = model_allocate (model_w, model_h);
   if (!model)
     {
@@ -267,9 +324,6 @@ screenhack (Display *dpy, Window window)
 
   fgc = XCreateGC (dpy, window, 0, &gcv);
 
-  x2 = random() % model_w;
-  y2 = random() % model_h;
-
   delay_usecs = get_integer_resource ("delay", "Integer");
   n_restart = get_integer_resource ("restart", "Integer");
     
@@ -280,10 +334,15 @@ screenhack (Display *dpy, Window window)
   while (1) {
     int i_batch;
 
-    if (!i_restart)
+    if (i_restart == 0)
       {
+       /* Time to start a new simulation, this one has probably got
+          to be a bit boring. */
        setup_colormap (dpy, &wattr, &colors, &n_colors);
+       erase_full_window (dpy, window);
        model_initialize (model);
+       pos = 1;
+       wrapped = 0;
       }
     
     for (i_batch = batchcount; i_batch; i_batch--)
@@ -296,27 +355,33 @@ screenhack (Display *dpy, Window window)
            XChangeGC (dpy, fgc, GCForeground, &gcv);
          }
        
-      /* draw a line */
-      x1 = x2;
-      y1 = y2;
+       assert(pos >= 0 && pos < settings.trail);
+       model_step (model, &history[pos]);
 
-      model_step (model, &x2, &y2);
+       draw_step (&settings, dpy, window, fgc, pos, history);
 
-      XDrawLine (dpy, window, fgc,
-                x1 * cell_size + cell_size/2,
-                y1 * cell_size + cell_size/2,
-                x2 * cell_size + cell_size/2,
-                y2 * cell_size + cell_size/2);
+       /* we use the history as a ring buffer, but don't start erasing until
+          we've wrapped around once. */
+       if (++pos >= settings.trail)
+         {
+           pos -= settings.trail;
+           wrapped = 1;
+         }
 
-      XSync (dpy, False); 
-      screenhack_handle_events (dpy);
+       if (wrapped)
+         {
+           draw_step (&settings, dpy, window, bgc, pos+1, history);
+         }
 
-      if (delay_usecs)
-        usleep (delay_usecs);
-    }
+       XSync (dpy, False); 
+       screenhack_handle_events (dpy);
+       
+       if (delay_usecs)
+         usleep (delay_usecs);
 
+      }
+    
     i_restart = (i_restart + 1) % n_restart;
-    erase_full_window (dpy, window);
   }
 }