4241950c0c386dc43a4cef014fb2358cf6c69a84
[xscreensaver] / hacks / goop.c
1 /* xscreensaver, Copyright (c) 1997, 2006 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #include <math.h>
13 #include "screenhack.h"
14 #include "spline.h"
15 #include "alpha.h"
16
17
18 /* This is pretty compute-intensive, probably due to the large number of
19    polygon fills.  I tried introducing a scaling factor to make the spline
20    code emit fewer line segments, but that made the edges very rough.
21    However, tuning *maxVelocity, *elasticity and *delay can result in much
22    smoother looking animation.  I tuned these for a 1280x1024 Indy display,
23    but I don't know whether these values will be reasonable for a slower
24    machine...
25
26    The more planes the better -- SGIs have a 12-bit pseudocolor display
27    (4096 colormap cells) which is mostly useless, except for this program,
28    where it means you can have 11 or 12 mutually-transparent objects instead
29    of only 7 or 8.  But, if you are using the 12-bit visual, you should crank
30    down the velocity and elasticity, or server slowness will cause the
31    animation to look jerky (yes, it's sad but true, SGI's X server is
32    perceptibly slower when using plane masks on a 12-bit visual than on an
33    8-bit visual.)  Using -max-velocity 0.5 -elasticity 0.9 seems to work ok
34    on my Indy R5k with visual 0x27 and the bottom-of-the-line 24-bit graphics
35    board.
36
37    It might look better if each blob had an outline, which was a *slightly*
38    darker color than the center, to give them a bit more definition -- but
39    that would mean using two planes per blob.  (Or maybe allocating the
40    outline colors outside of the plane-space?  Then the outlines wouldn't be
41    transparent, but maybe that wouldn't be so noticeable?)
42
43    Oh, for an alpha channel... maybe I should rewrite this in GL.  Then the
44    blobs could have thickness, and curved edges with specular reflections...
45  */
46
47 #define SCALE       10000  /* fixed-point math, for sub-pixel motion */
48 #define DEF_COUNT   12     /* When planes and count are 0, how many blobs. */
49
50
51 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
52 #define RANDSIGN() ((random() & 1) ? 1 : -1)
53
54 struct blob {
55   long x, y;            /* position of midpoint */
56   long dx, dy;          /* velocity and direction */
57   double torque;        /* rotational speed */
58   double th;            /* angle of rotation */
59   long elasticity;      /* how fast they deform */
60   long max_velocity;    /* speed limit */
61   long min_r, max_r;    /* radius range */
62   int npoints;          /* control points */
63   long *r;              /* radii */
64   spline *spline;
65 };
66
67 struct layer {
68   int nblobs;
69   struct blob **blobs;
70   Pixmap pixmap;
71   unsigned long pixel;
72   GC gc;
73 };
74
75 enum goop_mode {
76   transparent,
77   opaque,
78   xor,
79   outline
80 };
81
82 struct goop {
83   enum goop_mode mode;
84   int width, height;
85   int nlayers;
86   struct layer **layers;
87   unsigned long background;
88   Pixmap pixmap;
89   GC pixmap_gc;
90   GC window_gc;
91   Bool additive_p;
92   Bool cmap_p;
93   int delay;
94 };
95
96
97 static struct blob *
98 make_blob (Display *dpy, int maxx, int maxy, int size)
99 {
100   struct blob *b = (struct blob *) calloc(1, sizeof(*b));
101   int i;
102   int mid;
103
104   maxx *= SCALE;
105   maxy *= SCALE;
106   size *= SCALE;
107
108   b->max_r = size/2;
109   b->min_r = size/10;
110
111   if (b->min_r < (5*SCALE)) b->min_r = (5*SCALE);
112   mid = ((b->min_r + b->max_r) / 2);
113
114   b->torque       = get_float_resource (dpy, "torque", "Torque");
115   b->elasticity   = SCALE * get_float_resource (dpy, "elasticity", "Elasticity");
116   b->max_velocity = SCALE * get_float_resource (dpy, "maxVelocity", "MaxVelocity");
117
118   b->x = RAND(maxx);
119   b->y = RAND(maxy);
120
121   b->dx = RAND(b->max_velocity) * RANDSIGN();
122   b->dy = RAND(b->max_velocity) * RANDSIGN();
123   b->th = frand(M_PI+M_PI) * RANDSIGN();
124   b->npoints = (random() % 5) + 5;
125
126   b->spline = make_spline (b->npoints);
127   b->r = (long *) malloc (sizeof(*b->r) * b->npoints);
128   for (i = 0; i < b->npoints; i++)
129     b->r[i] = ((random() % mid) + (mid/2)) * RANDSIGN();
130   return b;
131 }
132
133 static void 
134 throb_blob (struct blob *b)
135 {
136   int i;
137   double frac = ((M_PI+M_PI) / b->npoints);
138
139   for (i = 0; i < b->npoints; i++)
140     {
141       long r = b->r[i];
142       long ra = (r > 0 ? r : -r);
143       double th = (b->th > 0 ? b->th : -b->th);
144       long x, y;
145
146       /* place control points evenly around perimiter, shifted by theta */
147       x = b->x + ra * cos (i * frac + th);
148       y = b->y + ra * sin (i * frac + th);
149
150       b->spline->control_x[i] = x / SCALE;
151       b->spline->control_y[i] = y / SCALE;
152
153       /* alter the radius by a random amount, in the direction in which
154          it had been going (the sign of the radius indicates direction.) */
155       ra += (RAND(b->elasticity) * (r > 0 ? 1 : -1));
156       r = ra * (r >= 0 ? 1 : -1);
157
158       /* If we've reached the end (too long or too short) reverse direction. */
159       if ((ra > b->max_r && r >= 0) ||
160           (ra < b->min_r && r < 0))
161         r = -r;
162       /* And reverse direction in mid-course once every 50 times. */
163       else if (! (random() % 50))
164         r = -r;
165
166       b->r[i] = r;
167     }
168 }
169
170 static void
171 move_blob (struct blob *b, int maxx, int maxy)
172 {
173   maxx *= SCALE;
174   maxy *= SCALE;
175
176   b->x += b->dx;
177   b->y += b->dy;
178
179   /* If we've reached the edge of the box, reverse direction. */
180   if ((b->x > maxx && b->dx >= 0) ||
181       (b->x < 0    && b->dx < 0))
182     {
183       b->dx = -b->dx;
184     }
185   if ((b->y > maxy && b->dy >= 0) ||
186       (b->y < 0    && b->dy < 0))
187     {
188       b->dy = -b->dy;
189     }
190
191   /* Alter velocity randomly. */
192   if (! (random() % 10))
193     {
194       b->dx += (RAND(b->max_velocity/2) * RANDSIGN());
195       b->dy += (RAND(b->max_velocity/2) * RANDSIGN());
196
197       /* Throttle velocity */
198       if (b->dx > b->max_velocity || b->dx < -b->max_velocity)
199         b->dx /= 2;
200       if (b->dy > b->max_velocity || b->dy < -b->max_velocity)
201         b->dy /= 2;
202     }
203
204   {
205     double th = b->th;
206     double d = (b->torque == 0 ? 0 : frand(b->torque));
207     if (th < 0)
208       th = -(th + d);
209     else
210       th += d;
211
212     if (th > (M_PI+M_PI))
213       th -= (M_PI+M_PI);
214     else if (th < 0)
215       th += (M_PI+M_PI);
216
217     b->th = (b->th > 0 ? th : -th);
218   }
219
220   /* Alter direction of rotation randomly. */
221   if (! (random() % 100))
222     b->th *= -1;
223 }
224
225 static void
226 draw_blob (Display *dpy, Drawable drawable, GC gc, struct blob *b,
227            Bool fill_p)
228 {
229   compute_closed_spline (b->spline);
230 #ifdef DEBUG
231   {
232     int i;
233     for (i = 0; i < b->npoints; i++)
234       XDrawLine (dpy, drawable, gc, b->x/SCALE, b->y/SCALE,
235                  b->spline->control_x[i], b->spline->control_y[i]);
236   }
237 #else
238   if (fill_p)
239     XFillPolygon (dpy, drawable, gc, b->spline->points, b->spline->n_points,
240                   Nonconvex, CoordModeOrigin);
241   else
242 #endif
243     XDrawLines (dpy, drawable, gc, b->spline->points, b->spline->n_points,
244                 CoordModeOrigin);
245 }
246
247
248 static struct layer *
249 make_layer (Display *dpy, Window window, int width, int height, int nblobs)
250 {
251   int i;
252   struct layer *layer = (struct layer *) calloc(1, sizeof(*layer));
253   int blob_min, blob_max;
254   XGCValues gcv;
255   layer->nblobs = nblobs;
256
257   layer->blobs = (struct blob **) malloc(sizeof(*layer->blobs)*layer->nblobs);
258
259   blob_max = (width < height ? width : height) / 2;
260   blob_min = (blob_max * 2) / 3;
261   for (i = 0; i < layer->nblobs; i++)
262     layer->blobs[i] = make_blob (dpy, width, height,
263                                  (random() % (blob_max-blob_min)) + blob_min);
264
265   layer->pixmap = XCreatePixmap (dpy, window, width, height, 1);
266   layer->gc = XCreateGC (dpy, layer->pixmap, 0, &gcv);
267
268 # ifdef HAVE_COCOA
269   jwxyz_XSetAlphaAllowed (dpy, layer->gc, True);
270 # endif /* HAVE_COCOA */
271
272   return layer;
273 }
274
275
276 #ifndef HAVE_COCOA
277 static void
278 draw_layer_plane (Display *dpy, struct layer *layer, int width, int height)
279 {
280   int i;
281   for (i = 0; i < layer->nblobs; i++)
282     {
283       throb_blob (layer->blobs[i]);
284       move_blob (layer->blobs[i], width, height);
285       draw_blob (dpy, layer->pixmap, layer->gc, layer->blobs[i], True);
286     }
287 }
288 #endif /* !HAVE_COCOA */
289
290
291 static void
292 draw_layer_blobs (Display *dpy, Drawable drawable, GC gc,
293                   struct layer *layer, int width, int height,
294                   Bool fill_p)
295 {
296   int i;
297   for (i = 0; i < layer->nblobs; i++)
298     {
299       draw_blob (dpy, drawable, gc, layer->blobs[i], fill_p);
300       throb_blob (layer->blobs[i]);
301       move_blob (layer->blobs[i], width, height);
302     }
303 }
304
305
306 static struct goop *
307 make_goop (Screen *screen, Visual *visual, Window window, Colormap cmap,
308            int width, int height, long depth)
309 {
310   Display *dpy = DisplayOfScreen (screen);
311   int i;
312   struct goop *goop = (struct goop *) calloc(1, sizeof(*goop));
313   XGCValues gcv;
314   int nblobs = get_integer_resource (dpy, "count", "Count");
315
316   unsigned long *plane_masks = 0;
317 # ifndef HAVE_COCOA
318   unsigned long base_pixel = 0;
319 # endif
320   char *s;
321
322   s = get_string_resource (dpy, "mode", "Mode");
323   goop->mode = transparent;
324   if (!s || !*s || !strcasecmp (s, "transparent"))
325     ;
326   else if (!strcasecmp (s, "opaque"))
327     goop->mode = opaque;
328   else if (!strcasecmp (s, "xor"))
329     goop->mode = xor;
330   else
331     fprintf (stderr, "%s: bogus mode: \"%s\"\n", progname, s);
332
333   goop->delay = get_integer_resource (dpy, "delay", "Integer");
334
335   goop->width = width;
336   goop->height = height;
337
338   goop->nlayers = get_integer_resource (dpy, "planes", "Planes");
339   if (goop->nlayers <= 0)
340     goop->nlayers = (random() % (depth-2)) + 2;
341   goop->layers = (struct layer **) malloc(sizeof(*goop->layers)*goop->nlayers);
342
343   goop->additive_p = get_boolean_resource (dpy, "additive", "Additive");
344   goop->cmap_p = has_writable_cells (screen, visual);
345
346   if (mono_p && goop->mode == transparent)
347     goop->mode = opaque;
348
349 # ifndef HAVE_COCOA
350   /* Try to allocate some color planes before committing to nlayers.
351    */
352   if (goop->mode == transparent)
353     {
354       int nplanes = goop->nlayers;
355       allocate_alpha_colors (screen, visual, cmap,
356                              &nplanes, goop->additive_p, &plane_masks,
357                              &base_pixel);
358       if (nplanes > 1)
359         goop->nlayers = nplanes;
360       else
361         {
362           fprintf (stderr,
363          "%s: couldn't allocate any color planes; turning transparency off.\n",
364                    progname);
365           goop->mode = opaque;
366         }
367     }
368 # endif /* !HAVE_COCOA */
369
370   {
371     int lblobs[32];
372     int total = DEF_COUNT;
373     memset (lblobs, 0, sizeof(lblobs));
374     if (nblobs <= 0)
375       while (total)
376         for (i = 0; total && i < goop->nlayers; i++)
377           lblobs[i]++, total--;
378     for (i = 0; i < goop->nlayers; i++)
379       goop->layers[i] = make_layer (dpy, window, width, height, 
380                                     (nblobs > 0 ? nblobs : lblobs[i]));
381   }
382
383 # ifndef HAVE_COCOA
384   if (goop->mode == transparent && plane_masks)
385     {
386       for (i = 0; i < goop->nlayers; i++)
387         goop->layers[i]->pixel = base_pixel | plane_masks[i];
388       goop->background = base_pixel;
389     }
390 # endif /* !HAVE_COCOA */
391
392   if (plane_masks)
393     free (plane_masks);
394
395 # ifndef HAVE_COCOA
396   if (goop->mode != transparent)
397 # endif /* !HAVE_COCOA */
398     {
399       XColor color;
400       color.flags = DoRed|DoGreen|DoBlue;
401
402       goop->background =
403         get_pixel_resource (dpy,cmap, "background", "Background");
404
405       for (i = 0; i < goop->nlayers; i++)
406         {
407           int H = random() % 360;                          /* range 0-360    */
408           double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
409           double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
410           hsv_to_rgb (H, S, V, &color.red, &color.green, &color.blue);
411           if (XAllocColor (dpy, cmap, &color))
412             goop->layers[i]->pixel = color.pixel;
413           else
414             goop->layers[i]->pixel =
415               WhitePixelOfScreen(DefaultScreenOfDisplay(dpy));
416 # ifdef HAVE_COCOA
417           if (goop->mode == transparent)
418             {
419               /* give a non-opaque alpha to the color */
420               unsigned long pixel = goop->layers[i]->pixel;
421               unsigned long amask = BlackPixelOfScreen (0);
422               unsigned long a = (0xBBBBBBBB & amask);
423               pixel = (pixel & (~amask)) | a;
424               goop->layers[i]->pixel = pixel;
425             }
426 # endif /* HAVE_COCOA */
427         }
428     }
429
430   goop->pixmap = XCreatePixmap (dpy, window, width, height,
431                                 (goop->mode == xor ? 1L : depth));
432
433   gcv.background = goop->background;
434   gcv.foreground = get_pixel_resource (dpy, cmap, "foreground", "Foreground");
435   gcv.line_width = get_integer_resource (dpy, "thickness","Thickness");
436   goop->pixmap_gc = XCreateGC (dpy, goop->pixmap, GCLineWidth, &gcv);
437   goop->window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
438
439 # ifdef HAVE_COCOA
440   jwxyz_XSetAlphaAllowed (dpy, goop->pixmap_gc, True);
441 # endif /* HAVE_COCOA */
442   
443   return goop;
444 }
445
446 static void *
447 goop_init (Display *dpy, Window window)
448 {
449   XWindowAttributes xgwa;
450   XGetWindowAttributes (dpy, window, &xgwa);
451   return make_goop (xgwa.screen, xgwa.visual, window, xgwa.colormap,
452                     xgwa.width, xgwa.height, xgwa.depth);
453 }
454
455 static unsigned long
456 goop_draw (Display *dpy, Window window, void *closure)
457 {
458   struct goop *goop = (struct goop *) closure;
459   int i;
460
461   switch (goop->mode)
462     {
463 # ifndef HAVE_COCOA
464     case transparent:
465
466       for (i = 0; i < goop->nlayers; i++)
467         draw_layer_plane (dpy, goop->layers[i], goop->width, goop->height);
468
469       XSetForeground (dpy, goop->pixmap_gc, goop->background);
470       XSetFunction (dpy, goop->pixmap_gc, GXcopy);
471       XSetPlaneMask (dpy, goop->pixmap_gc, AllPlanes);
472       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
473                       goop->width, goop->height);
474
475       XSetForeground (dpy, goop->pixmap_gc, ~0L);
476
477       if (!goop->cmap_p && !goop->additive_p)
478         {
479           int j;
480           for (i = 0; i < goop->nlayers; i++)
481             for (j = 0; j < goop->layers[i]->nblobs; j++)
482               draw_blob (dpy, goop->pixmap, goop->pixmap_gc,
483                          goop->layers[i]->blobs[j], True);
484           XSetFunction (dpy, goop->pixmap_gc, GXclear);
485         }
486
487       for (i = 0; i < goop->nlayers; i++)
488         {
489           XSetPlaneMask (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
490           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
491                             goop->layers[i], goop->width, goop->height,
492                             True);
493         }
494       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
495                  goop->width, goop->height, 0, 0);
496       break;
497 #endif /* !HAVE_COCOA */
498
499     case xor:
500       XSetFunction (dpy, goop->pixmap_gc, GXcopy);
501       XSetForeground (dpy, goop->pixmap_gc, 0);
502       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
503                       goop->width, goop->height);
504       XSetFunction (dpy, goop->pixmap_gc, GXxor);
505       XSetForeground (dpy, goop->pixmap_gc, 1);
506       for (i = 0; i < goop->nlayers; i++)
507         draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
508                           goop->layers[i], goop->width, goop->height,
509                           (goop->mode != outline));
510       XCopyPlane (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
511                   goop->width, goop->height, 0, 0, 1L);
512       break;
513
514 # ifdef HAVE_COCOA
515     case transparent:
516 # endif
517     case opaque:
518     case outline:
519       XSetForeground (dpy, goop->pixmap_gc, goop->background);
520       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
521                       goop->width, goop->height);
522       for (i = 0; i < goop->nlayers; i++)
523         {
524           XSetForeground (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
525           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
526                             goop->layers[i], goop->width, goop->height,
527                             (goop->mode != outline));
528         }
529       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
530                  goop->width, goop->height, 0, 0);
531       break;
532
533     default:
534       abort ();
535       break;
536     }
537   return goop->delay;
538 }
539
540 static void
541 goop_reshape (Display *dpy, Window window, void *closure, 
542                  unsigned int w, unsigned int h)
543 {
544   /* #### write me */
545 }
546
547 static Bool
548 goop_event (Display *dpy, Window window, void *closure, XEvent *event)
549 {
550   return False;
551 }
552
553 static void
554 goop_free (Display *dpy, Window window, void *closure)
555 {
556 }
557
558
559 \f
560
561 static const char *goop_defaults [] = {
562   ".background:         black",
563   ".foreground:         yellow",
564   "*delay:              12000",
565   "*additive:           true",
566   "*mode:               transparent",
567   "*count:              0",
568   "*planes:             0",
569   "*thickness:          5",
570   "*torque:             0.0075",
571   "*elasticity:         0.9",
572   "*maxVelocity:        0.5",
573   0
574 };
575
576 static XrmOptionDescRec goop_options [] = {
577   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
578   { "-count",           ".count",       XrmoptionSepArg, 0 },
579   { "-planes",          ".planes",      XrmoptionSepArg, 0 },
580   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
581   { "-xor",             ".mode",        XrmoptionNoArg, "xor" },
582   { "-transparent",     ".mode",        XrmoptionNoArg, "transparent" },
583   { "-opaque",          ".mode",        XrmoptionNoArg, "opaque" },
584   { "-additive",        ".additive",    XrmoptionNoArg, "True" },
585   { "-subtractive",     ".additive",    XrmoptionNoArg, "false" },
586   { "-thickness",       ".thickness",   XrmoptionSepArg, 0 },
587   { "-torque",          ".torque",      XrmoptionSepArg, 0 },
588   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
589   { "-max-velocity",    ".maxVelocity", XrmoptionSepArg, 0 },
590   { 0, 0, 0, 0 }
591 };
592
593 XSCREENSAVER_MODULE ("Goop", goop)