ftp://ftp.smr.ru/pub/0/FreeBSD/releases/distfiles/xscreensaver-3.16.tar.gz
[xscreensaver] / hacks / goop.c
diff --git a/hacks/goop.c b/hacks/goop.c
new file mode 100644 (file)
index 0000000..ace2352
--- /dev/null
@@ -0,0 +1,543 @@
+/* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation.  No representations are made about the suitability of this
+ * software for any purpose.  It is provided "as is" without express or 
+ * implied warranty.
+ */
+
+#include <math.h>
+#include "screenhack.h"
+#include "spline.h"
+#include "alpha.h"
+
+
+/* This is pretty compute-intensive, probably due to the large number of
+   polygon fills.  I tried introducing a scaling factor to make the spline
+   code emit fewer line segments, but that made the edges very rough.
+   However, tuning *maxVelocity, *elasticity and *delay can result in much
+   smoother looking animation.  I tuned these for a 1280x1024 Indy display,
+   but I don't know whether these values will be reasonable for a slower
+   machine...
+
+   The more planes the better -- SGIs have a 12-bit pseudocolor display
+   (4096 colormap cells) which is mostly useless, except for this program,
+   where it means you can have 11 or 12 mutually-transparent objects instead
+   of only 7 or 8.  But, if you are using the 12-bit visual, you should crank
+   down the velocity and elasticity, or server slowness will cause the
+   animation to look jerky (yes, it's sad but true, SGI's X server is
+   perceptibly slower when using plane masks on a 12-bit visual than on an
+   8-bit visual.)  Using -max-velocity 0.5 -elasticity 0.9 seems to work ok
+   on my Indy R5k with visual 0x27 and the bottom-of-the-line 24-bit graphics
+   board.
+
+   It might look better if each blob had an outline, which was a *slightly*
+   darker color than the center, to give them a bit more definition -- but
+   that would mean using two planes per blob.  (Or maybe allocating the
+   outline colors outside of the plane-space?  Then the outlines wouldn't be
+   transparent, but maybe that wouldn't be so noticeable?)
+
+   Oh, for an alpha channel... maybe I should rewrite this in GL.  Then the
+   blobs could have thickness, and curved edges with specular reflections...
+ */
+
+
+#define SCALE       10000  /* fixed-point math, for sub-pixel motion */
+#define DEF_COUNT   12    /* When planes and count are 0, how many blobs. */
+
+
+#define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
+#define RANDSIGN() ((random() & 1) ? 1 : -1)
+
+struct blob {
+  long x, y;           /* position of midpoint */
+  long dx, dy;         /* velocity and direction */
+  double torque;       /* rotational speed */
+  double th;           /* angle of rotation */
+  long elasticity;     /* how fast they deform */
+  long max_velocity;   /* speed limit */
+  long min_r, max_r;   /* radius range */
+  int npoints;         /* control points */
+  long *r;             /* radii */
+  spline *spline;
+};
+
+struct layer {
+  int nblobs;
+  struct blob **blobs;
+  Pixmap pixmap;
+  unsigned long pixel;
+  GC gc;
+};
+
+enum goop_mode {
+  transparent,
+  opaque,
+  xor,
+  outline
+};
+
+struct goop {
+  enum goop_mode mode;
+  int width, height;
+  int nlayers;
+  struct layer **layers;
+  unsigned long background;
+  Pixmap pixmap;
+  GC pixmap_gc;
+  GC window_gc;
+  Bool additive_p;
+  Bool cmap_p;
+};
+
+
+static struct blob *
+make_blob (int maxx, int maxy, int size)
+{
+  struct blob *b = (struct blob *) calloc(1, sizeof(*b));
+  int i;
+  int mid;
+
+  maxx *= SCALE;
+  maxy *= SCALE;
+  size *= SCALE;
+
+  b->max_r = size/2;
+  b->min_r = size/10;
+
+  if (b->min_r < (5*SCALE)) b->min_r = (5*SCALE);
+  mid = ((b->min_r + b->max_r) / 2);
+
+  b->torque       = get_float_resource ("torque", "Torque");
+  b->elasticity   = SCALE * get_float_resource ("elasticity", "Elasticity");
+  b->max_velocity = SCALE * get_float_resource ("maxVelocity", "MaxVelocity");
+
+  b->x = RAND(maxx);
+  b->y = RAND(maxy);
+
+  b->dx = RAND(b->max_velocity) * RANDSIGN();
+  b->dy = RAND(b->max_velocity) * RANDSIGN();
+  b->th = frand(M_PI+M_PI) * RANDSIGN();
+  b->npoints = (random() % 5) + 5;
+
+  b->spline = make_spline (b->npoints);
+  b->r = (long *) malloc (sizeof(*b->r) * b->npoints);
+  for (i = 0; i < b->npoints; i++)
+    b->r[i] = ((random() % mid) + (mid/2)) * RANDSIGN();
+  return b;
+}
+
+static void 
+throb_blob (struct blob *b)
+{
+  int i;
+  double frac = ((M_PI+M_PI) / b->npoints);
+
+  for (i = 0; i < b->npoints; i++)
+    {
+      long r = b->r[i];
+      long ra = (r > 0 ? r : -r);
+      double th = (b->th > 0 ? b->th : -b->th);
+      long x, y;
+
+      /* place control points evenly around perimiter, shifted by theta */
+      x = b->x + ra * cos (i * frac + th);
+      y = b->y + ra * sin (i * frac + th);
+
+      b->spline->control_x[i] = x / SCALE;
+      b->spline->control_y[i] = y / SCALE;
+
+      /* alter the radius by a random amount, in the direction in which
+        it had been going (the sign of the radius indicates direction.) */
+      ra += (RAND(b->elasticity) * (r > 0 ? 1 : -1));
+      r = ra * (r >= 0 ? 1 : -1);
+
+      /* If we've reached the end (too long or too short) reverse direction. */
+      if ((ra > b->max_r && r >= 0) ||
+         (ra < b->min_r && r < 0))
+       r = -r;
+      /* And reverse direction in mid-course once every 50 times. */
+      else if (! (random() % 50))
+       r = -r;
+
+      b->r[i] = r;
+    }
+}
+
+static void
+move_blob (struct blob *b, int maxx, int maxy)
+{
+  maxx *= SCALE;
+  maxy *= SCALE;
+
+  b->x += b->dx;
+  b->y += b->dy;
+
+  /* If we've reached the edge of the box, reverse direction. */
+  if ((b->x > maxx && b->dx >= 0) ||
+      (b->x < 0    && b->dx < 0))
+    {
+      b->dx = -b->dx;
+    }
+  if ((b->y > maxy && b->dy >= 0) ||
+      (b->y < 0    && b->dy < 0))
+    {
+      b->dy = -b->dy;
+    }
+
+  /* Alter velocity randomly. */
+  if (! (random() % 10))
+    {
+      b->dx += (RAND(b->max_velocity/2) * RANDSIGN());
+      b->dy += (RAND(b->max_velocity/2) * RANDSIGN());
+
+      /* Throttle velocity */
+      if (b->dx > b->max_velocity || b->dx < -b->max_velocity)
+       b->dx /= 2;
+      if (b->dy > b->max_velocity || b->dy < -b->max_velocity)
+       b->dy /= 2;
+    }
+
+  {
+    double th = b->th;
+    double d = (b->torque == 0 ? 0 : frand(b->torque));
+    if (th < 0)
+      th = -(th + d);
+    else
+      th += d;
+
+    if (th > (M_PI+M_PI))
+      th -= (M_PI+M_PI);
+    else if (th < 0)
+      th += (M_PI+M_PI);
+
+    b->th = (b->th > 0 ? th : -th);
+  }
+
+  /* Alter direction of rotation randomly. */
+  if (! (random() % 100))
+    b->th *= -1;
+}
+
+static void
+draw_blob (Display *dpy, Drawable drawable, GC gc, struct blob *b,
+          Bool fill_p)
+{
+  compute_closed_spline (b->spline);
+#ifdef DEBUG
+  {
+    int i;
+    for (i = 0; i < b->npoints; i++)
+      XDrawLine (dpy, drawable, gc, b->x/SCALE, b->y/SCALE,
+                b->spline->control_x[i], b->spline->control_y[i]);
+  }
+#else
+  if (fill_p)
+    XFillPolygon (dpy, drawable, gc, b->spline->points, b->spline->n_points,
+                 Nonconvex, CoordModeOrigin);
+  else
+#endif
+    XDrawLines (dpy, drawable, gc, b->spline->points, b->spline->n_points,
+               CoordModeOrigin);
+}
+
+
+static struct layer *
+make_layer (Display *dpy, Window window, int width, int height, int nblobs)
+{
+  int i;
+  struct layer *layer = (struct layer *) calloc(1, sizeof(*layer));
+  int blob_min, blob_max;
+  XGCValues gcv;
+  layer->nblobs = nblobs;
+
+  layer->blobs = (struct blob **) malloc(sizeof(*layer->blobs)*layer->nblobs);
+
+  blob_max = (width < height ? width : height) / 2;
+  blob_min = (blob_max * 2) / 3;
+  for (i = 0; i < layer->nblobs; i++)
+    layer->blobs[i] = make_blob (width, height,
+                                (random() % (blob_max-blob_min)) + blob_min);
+
+  layer->pixmap = XCreatePixmap (dpy, window, width, height, 1);
+  layer->gc = XCreateGC (dpy, layer->pixmap, 0, &gcv);
+
+  return layer;
+}
+
+static void
+draw_layer_plane (Display *dpy, struct layer *layer, int width, int height)
+{
+  int i;
+  for (i = 0; i < layer->nblobs; i++)
+    {
+      throb_blob (layer->blobs[i]);
+      move_blob (layer->blobs[i], width, height);
+      draw_blob (dpy, layer->pixmap, layer->gc, layer->blobs[i], True);
+    }
+}
+
+
+static void
+draw_layer_blobs (Display *dpy, Drawable drawable, GC gc,
+                 struct layer *layer, int width, int height,
+                 Bool fill_p)
+{
+  int i;
+  for (i = 0; i < layer->nblobs; i++)
+    {
+      draw_blob (dpy, drawable, gc, layer->blobs[i], fill_p);
+      throb_blob (layer->blobs[i]);
+      move_blob (layer->blobs[i], width, height);
+    }
+}
+
+
+static struct goop *
+make_goop (Screen *screen, Visual *visual, Window window, Colormap cmap,
+          int width, int height, long depth)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  int i;
+  struct goop *goop = (struct goop *) calloc(1, sizeof(*goop));
+  XGCValues gcv;
+  int nblobs = get_integer_resource ("count", "Count");
+
+  unsigned long *plane_masks = 0;
+  unsigned long base_pixel = 0;
+
+  goop->mode = (get_boolean_resource("xor", "Xor")
+               ? xor
+               : (get_boolean_resource("transparent", "Transparent")
+                  ? transparent
+                  : opaque));
+
+  goop->width = width;
+  goop->height = height;
+
+
+  goop->nlayers = get_integer_resource ("planes", "Planes");
+  if (goop->nlayers <= 0)
+    goop->nlayers = (random() % (depth-2)) + 2;
+  goop->layers = (struct layer **) malloc(sizeof(*goop->layers)*goop->nlayers);
+
+  goop->additive_p = get_boolean_resource ("additive", "Additive");
+  goop->cmap_p = has_writable_cells (screen, visual);
+
+  if (mono_p && goop->mode == transparent)
+    goop->mode = opaque;
+
+  /* Try to allocate some color planes before committing to nlayers.
+   */
+  if (goop->mode == transparent)
+    {
+      int nplanes = goop->nlayers;
+      allocate_alpha_colors (screen, visual, cmap,
+                             &nplanes, goop->additive_p, &plane_masks,
+                            &base_pixel);
+      if (nplanes > 1)
+       goop->nlayers = nplanes;
+      else
+       {
+         fprintf (stderr,
+         "%s: couldn't allocate any color planes; turning transparency off.\n",
+                  progname);
+         goop->mode = opaque;
+       }
+    }
+
+  {
+    int lblobs[32];
+    int total = DEF_COUNT;
+    memset (lblobs, 0, sizeof(lblobs));
+    if (nblobs <= 0)
+      while (total)
+       for (i = 0; total && i < goop->nlayers; i++)
+         lblobs[i]++, total--;
+    for (i = 0; i < goop->nlayers; i++)
+      goop->layers[i] = make_layer (dpy, window, width, height, 
+                                   (nblobs > 0 ? nblobs : lblobs[i]));
+  }
+
+  if (goop->mode == transparent && plane_masks)
+    {
+      for (i = 0; i < goop->nlayers; i++)
+       goop->layers[i]->pixel = base_pixel | plane_masks[i];
+      goop->background = base_pixel;
+    }
+  if (plane_masks)
+    free (plane_masks);
+
+  if (goop->mode != transparent)
+    {
+      XColor color;
+      color.flags = DoRed|DoGreen|DoBlue;
+
+      goop->background =
+       get_pixel_resource ("background", "Background", dpy,cmap);
+
+      for (i = 0; i < goop->nlayers; i++)
+       {
+         int H = random() % 360;                          /* range 0-360    */
+         double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
+         double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
+         hsv_to_rgb (H, S, V, &color.red, &color.green, &color.blue);
+         if (XAllocColor (dpy, cmap, &color))
+           goop->layers[i]->pixel = color.pixel;
+         else
+           goop->layers[i]->pixel =
+             WhitePixelOfScreen(DefaultScreenOfDisplay(dpy));
+       }
+    }
+
+  goop->pixmap = XCreatePixmap (dpy, window, width, height,
+                               (goop->mode == xor ? 1L : depth));
+
+  gcv.background = goop->background;
+  gcv.foreground = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
+  gcv.line_width = get_integer_resource ("thickness","Thickness");
+  goop->pixmap_gc = XCreateGC (dpy, goop->pixmap, GCLineWidth, &gcv);
+  goop->window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
+
+  return goop;
+}
+
+static struct goop *
+init_goop (Display *dpy, Window window)
+{
+  XWindowAttributes xgwa;
+  XGetWindowAttributes (dpy, window, &xgwa);
+
+  return make_goop (xgwa.screen, xgwa.visual, window, xgwa.colormap,
+                   xgwa.width, xgwa.height, xgwa.depth);
+}
+
+static void
+run_goop (Display *dpy, Window window, struct goop *goop)
+{
+  int i, j;
+
+  switch (goop->mode)
+    {
+    case transparent:
+
+      for (i = 0; i < goop->nlayers; i++)
+       draw_layer_plane (dpy, goop->layers[i], goop->width, goop->height);
+
+      XSetForeground (dpy, goop->pixmap_gc, goop->background);
+      XSetFunction (dpy, goop->pixmap_gc, GXcopy);
+      XSetPlaneMask (dpy, goop->pixmap_gc, AllPlanes);
+      XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
+                     goop->width, goop->height);
+
+      XSetForeground (dpy, goop->pixmap_gc, ~0L);
+
+      if (!goop->cmap_p && !goop->additive_p)
+        {
+          for (i = 0; i < goop->nlayers; i++)
+            for (j = 0; j < goop->layers[i]->nblobs; j++)
+              draw_blob (dpy, goop->pixmap, goop->pixmap_gc,
+                         goop->layers[i]->blobs[j], True);
+          XSetFunction (dpy, goop->pixmap_gc, GXclear);
+        }
+
+      for (i = 0; i < goop->nlayers; i++)
+       {
+         XSetPlaneMask (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
+         draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
+                           goop->layers[i], goop->width, goop->height,
+                           True);
+       }
+      XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
+                goop->width, goop->height, 0, 0);
+      break;
+
+    case xor:
+      XSetFunction (dpy, goop->pixmap_gc, GXcopy);
+      XSetForeground (dpy, goop->pixmap_gc, 0);
+      XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
+                     goop->width, goop->height);
+      XSetFunction (dpy, goop->pixmap_gc, GXxor);
+      XSetForeground (dpy, goop->pixmap_gc, 1);
+      for (i = 0; i < goop->nlayers; i++)
+       draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
+                         goop->layers[i], goop->width, goop->height,
+                         (goop->mode != outline));
+      XCopyPlane (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
+                 goop->width, goop->height, 0, 0, 1L);
+      break;
+
+    case opaque:
+    case outline:
+      XSetForeground (dpy, goop->pixmap_gc, goop->background);
+      XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
+                     goop->width, goop->height);
+      for (i = 0; i < goop->nlayers; i++)
+       {
+         XSetForeground (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
+         draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
+                           goop->layers[i], goop->width, goop->height,
+                           (goop->mode != outline));
+       }
+      XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
+                goop->width, goop->height, 0, 0);
+      break;
+
+    default:
+      abort ();
+      break;
+    }
+}
+
+\f
+char *progclass = "Goop";
+
+char *defaults [] = {
+  ".background:                black",
+  ".foreground:                white",
+  "*delay:             12000",
+  "*transparent:       true",
+  "*additive:          true",
+  "*xor:               false",
+  "*count:             0",
+  "*planes:            0",
+  "*thickness:         5",
+  "*torque:            0.0075",
+  "*elasticity:                1.8",
+  "*maxVelocity:       1.2",
+  0
+};
+
+XrmOptionDescRec options [] = {
+  { "-delay",          ".delay",       XrmoptionSepArg, 0 },
+  { "-count",          ".count",       XrmoptionSepArg, 0 },
+  { "-planes",         ".planes",      XrmoptionSepArg, 0 },
+  { "-transparent",    ".transparent", XrmoptionNoArg, "True" },
+  { "-non-transparent",        ".transparent", XrmoptionNoArg, "False" },
+  { "-additive",       ".additive",    XrmoptionNoArg, "True" },
+  { "-subtractive",    ".additive",    XrmoptionNoArg, "false" },
+  { "-xor",            ".xor",         XrmoptionNoArg, "true" },
+  { "-no-xor",         ".xor",         XrmoptionNoArg, "false" },
+  { "-thickness",      ".thickness",   XrmoptionSepArg, 0 },
+  { "-torque",         ".torque",      XrmoptionSepArg, 0 },
+  { "-elasticity",     ".elasticity",  XrmoptionSepArg, 0 },
+  { "-max-velocity",   ".maxVelocity", XrmoptionSepArg, 0 },
+  { 0, 0, 0, 0 }
+};
+
+void
+screenhack (Display *dpy, Window window)
+{
+  struct goop *g = init_goop (dpy, window);
+  int delay = get_integer_resource ("delay", "Integer");
+  while (1)
+    {
+      run_goop (dpy, window, g);
+      XSync (dpy, False);
+      screenhack_handle_events (dpy);
+      if (delay) usleep (delay);
+    }
+}