119fec55f277ccda63c42010aa6ee06260522d89
[xscreensaver] / hacks / goop.c
1 /* xscreensaver, Copyright (c) 1997-2008 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 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
51 #define RANDSIGN() ((random() & 1) ? 1 : -1)
52
53 struct blob {
54   long x, y;            /* position of midpoint */
55   long dx, dy;          /* velocity and direction */
56   double torque;        /* rotational speed */
57   double th;            /* angle of rotation */
58   long elasticity;      /* how fast they deform */
59   long max_velocity;    /* speed limit */
60   long min_r, max_r;    /* radius range */
61   int npoints;          /* control points */
62   long *r;              /* radii */
63   spline *spline;
64 };
65
66 struct layer {
67   int nblobs;
68   struct blob **blobs;
69   Pixmap pixmap;
70   unsigned long pixel;
71   GC gc;
72 };
73
74 enum goop_mode {
75   transparent,
76   opaque,
77   xor,
78   outline
79 };
80
81 struct goop {
82   enum goop_mode mode;
83   int width, height;
84   int nlayers;
85   struct layer **layers;
86   unsigned long background;
87   Pixmap pixmap;
88   GC pixmap_gc;
89   GC window_gc;
90   Bool additive_p;
91   Bool cmap_p;
92   int delay;
93 };
94
95
96 static struct blob *
97 make_blob (Display *dpy, int maxx, int maxy, int size)
98 {
99   struct blob *b = (struct blob *) calloc(1, sizeof(*b));
100   int i;
101   int mid;
102
103   maxx *= SCALE;
104   maxy *= SCALE;
105   size *= SCALE;
106
107   b->max_r = size/2;
108   b->min_r = size/10;
109
110   if (b->min_r < (5*SCALE)) b->min_r = (5*SCALE);
111   mid = ((b->min_r + b->max_r) / 2);
112
113   b->torque       = get_float_resource (dpy, "torque", "Torque");
114   b->elasticity   = SCALE * get_float_resource (dpy, "elasticity", "Elasticity");
115   b->max_velocity = SCALE * get_float_resource (dpy, "maxVelocity", "MaxVelocity");
116
117   b->x = RAND(maxx);
118   b->y = RAND(maxy);
119
120   b->dx = RAND(b->max_velocity) * RANDSIGN();
121   b->dy = RAND(b->max_velocity) * RANDSIGN();
122   b->th = frand(M_PI+M_PI) * RANDSIGN();
123   b->npoints = (random() % 5) + 5;
124
125   b->spline = make_spline (b->npoints);
126   b->r = (long *) malloc (sizeof(*b->r) * b->npoints);
127   for (i = 0; i < b->npoints; i++)
128     b->r[i] = (long) ((random() % mid) + (mid/2)) * RANDSIGN();
129   return b;
130 }
131
132 static void
133 free_blob(struct blob *blob)
134 {
135   free_spline(blob->spline);
136   free(blob->r);
137   free(blob);
138 }
139
140 static void 
141 throb_blob (struct blob *b)
142 {
143   int i;
144   double frac = ((M_PI+M_PI) / b->npoints);
145
146   for (i = 0; i < b->npoints; i++)
147     {
148       long r = b->r[i];
149       long ra = (r > 0 ? r : -r);
150       double th = (b->th > 0 ? b->th : -b->th);
151       long x, y;
152
153       /* place control points evenly around perimiter, shifted by theta */
154       x = b->x + ra * cos (i * frac + th);
155       y = b->y + ra * sin (i * frac + th);
156
157       b->spline->control_x[i] = x / SCALE;
158       b->spline->control_y[i] = y / SCALE;
159
160       /* alter the radius by a random amount, in the direction in which
161          it had been going (the sign of the radius indicates direction.) */
162       ra += (RAND(b->elasticity) * (r > 0 ? 1 : -1));
163       r = ra * (r >= 0 ? 1 : -1);
164
165       /* If we've reached the end (too long or too short) reverse direction. */
166       if ((ra > b->max_r && r >= 0) ||
167           (ra < b->min_r && r < 0))
168         r = -r;
169       /* And reverse direction in mid-course once every 50 times. */
170       else if (! (random() % 50))
171         r = -r;
172
173       b->r[i] = r;
174     }
175 }
176
177 static void
178 move_blob (struct blob *b, int maxx, int maxy)
179 {
180   maxx *= SCALE;
181   maxy *= SCALE;
182
183   b->x += b->dx;
184   b->y += b->dy;
185
186   /* If we've reached the edge of the box, reverse direction. */
187   if ((b->x > maxx && b->dx >= 0) ||
188       (b->x < 0    && b->dx < 0))
189     {
190       b->dx = -b->dx;
191     }
192   if ((b->y > maxy && b->dy >= 0) ||
193       (b->y < 0    && b->dy < 0))
194     {
195       b->dy = -b->dy;
196     }
197
198   /* Alter velocity randomly. */
199   if (! (random() % 10))
200     {
201       b->dx += (RAND(b->max_velocity/2) * RANDSIGN());
202       b->dy += (RAND(b->max_velocity/2) * RANDSIGN());
203
204       /* Throttle velocity */
205       if (b->dx > b->max_velocity || b->dx < -b->max_velocity)
206         b->dx /= 2;
207       if (b->dy > b->max_velocity || b->dy < -b->max_velocity)
208         b->dy /= 2;
209     }
210
211   {
212     double th = b->th;
213     double d = (b->torque == 0 ? 0 : frand(b->torque));
214     if (th < 0)
215       th = -(th + d);
216     else
217       th += d;
218
219     if (th > (M_PI+M_PI))
220       th -= (M_PI+M_PI);
221     else if (th < 0)
222       th += (M_PI+M_PI);
223
224     b->th = (b->th > 0 ? th : -th);
225   }
226
227   /* Alter direction of rotation randomly. */
228   if (! (random() % 100))
229     b->th *= -1;
230 }
231
232 static void
233 draw_blob (Display *dpy, Drawable drawable, GC gc, struct blob *b,
234            Bool fill_p)
235 {
236   compute_closed_spline (b->spline);
237 #ifdef DEBUG
238   {
239     int i;
240     for (i = 0; i < b->npoints; i++)
241       XDrawLine (dpy, drawable, gc, b->x/SCALE, b->y/SCALE,
242                  b->spline->control_x[i], b->spline->control_y[i]);
243   }
244 #else
245   if (fill_p)
246     XFillPolygon (dpy, drawable, gc, b->spline->points, b->spline->n_points,
247                   Nonconvex, CoordModeOrigin);
248   else
249 #endif
250     XDrawLines (dpy, drawable, gc, b->spline->points, b->spline->n_points,
251                 CoordModeOrigin);
252 }
253
254
255 static struct layer *
256 make_layer (Display *dpy, Window window, int width, int height, int nblobs)
257 {
258   int i;
259   struct layer *layer = (struct layer *) calloc(1, sizeof(*layer));
260   int blob_min, blob_max;
261   XGCValues gcv;
262   layer->nblobs = nblobs;
263
264   layer->blobs = (struct blob **) malloc(sizeof(*layer->blobs)*layer->nblobs);
265
266   blob_max = (width < height ? width : height) / 2;
267   blob_min = (blob_max * 2) / 3;
268   for (i = 0; i < layer->nblobs; i++){
269     int j = blob_max - blob_min;
270     layer->blobs[i] = make_blob (dpy, width, height,
271                                  (j ? random() % j : 0) + blob_min);
272   }
273
274   layer->pixmap = XCreatePixmap (dpy, window, width, height, 1);
275   layer->gc = XCreateGC (dpy, layer->pixmap, 0, &gcv);
276
277 # ifdef HAVE_COCOA
278   jwxyz_XSetAlphaAllowed (dpy, layer->gc, True);
279 # endif /* HAVE_COCOA */
280
281   return layer;
282 }
283
284 static void
285 free_layer(struct layer *layer, Display *dpy)
286 {
287   int i;
288   for (i = 0; i < layer->nblobs; i++){
289     free_blob(layer->blobs[i]);
290   }
291   free(layer->blobs);
292   XFreeGC(dpy, layer->gc);
293   free(layer);
294 }
295
296
297 #ifndef HAVE_COCOA
298 static void
299 draw_layer_plane (Display *dpy, struct layer *layer, int width, int height)
300 {
301   int i;
302   for (i = 0; i < layer->nblobs; i++)
303     {
304       throb_blob (layer->blobs[i]);
305       move_blob (layer->blobs[i], width, height);
306       draw_blob (dpy, layer->pixmap, layer->gc, layer->blobs[i], True);
307     }
308 }
309 #endif /* !HAVE_COCOA */
310
311
312 static void
313 draw_layer_blobs (Display *dpy, Drawable drawable, GC gc,
314                   struct layer *layer, int width, int height,
315                   Bool fill_p)
316 {
317   int i;
318   for (i = 0; i < layer->nblobs; i++)
319     {
320       draw_blob (dpy, drawable, gc, layer->blobs[i], fill_p);
321       throb_blob (layer->blobs[i]);
322       move_blob (layer->blobs[i], width, height);
323     }
324 }
325
326
327 static struct goop *
328 make_goop (Screen *screen, Visual *visual, Window window, Colormap cmap,
329            int width, int height, long depth)
330 {
331   Display *dpy = DisplayOfScreen (screen);
332   int i;
333   struct goop *goop = (struct goop *) calloc(1, sizeof(*goop));
334   XGCValues gcv;
335   int nblobs = get_integer_resource (dpy, "count", "Count");
336
337   unsigned long *plane_masks = 0;
338 # ifndef HAVE_COCOA
339   unsigned long base_pixel = 0;
340 # endif
341   char *s;
342
343   s = get_string_resource (dpy, "mode", "Mode");
344   goop->mode = transparent;
345   if (!s || !*s || !strcasecmp (s, "transparent"))
346     ;
347   else if (!strcasecmp (s, "opaque"))
348     goop->mode = opaque;
349   else if (!strcasecmp (s, "xor"))
350     goop->mode = xor;
351   else
352     fprintf (stderr, "%s: bogus mode: \"%s\"\n", progname, s);
353   free(s);
354
355   goop->delay = get_integer_resource (dpy, "delay", "Integer");
356
357   goop->width = width;
358   goop->height = height;
359
360   goop->nlayers = get_integer_resource (dpy, "planes", "Planes");
361   if (goop->nlayers <= 0)
362     goop->nlayers = (random() % (depth-2)) + 2;
363   if (! goop->layers)
364     goop->layers = (struct layer **) 
365       malloc(sizeof(*goop->layers)*goop->nlayers);
366
367   goop->additive_p = get_boolean_resource (dpy, "additive", "Additive");
368   goop->cmap_p = has_writable_cells (screen, visual);
369
370   if (mono_p && goop->mode == transparent)
371     goop->mode = opaque;
372
373 # ifndef HAVE_COCOA
374   /* Try to allocate some color planes before committing to nlayers.
375    */
376   if (goop->mode == transparent)
377     {
378       int nplanes = goop->nlayers;
379       allocate_alpha_colors (screen, visual, cmap,
380                              &nplanes, goop->additive_p, &plane_masks,
381                              &base_pixel);
382       if (nplanes > 1)
383         goop->nlayers = nplanes;
384       else
385         {
386           fprintf (stderr,
387          "%s: couldn't allocate any color planes; turning transparency off.\n",
388                    progname);
389           goop->mode = opaque;
390         }
391     }
392 # endif /* !HAVE_COCOA */
393
394   {
395     int lblobs[32];
396     int total = DEF_COUNT;
397     memset (lblobs, 0, sizeof(lblobs));
398     if (nblobs <= 0)
399       while (total)
400         for (i = 0; total && i < goop->nlayers; i++)
401           lblobs[i]++, total--;
402     for (i = 0; i < goop->nlayers; i++)
403       goop->layers[i] = make_layer (dpy, window, width, height, 
404                                     (nblobs > 0 ? nblobs : lblobs[i]));
405   }
406
407 # ifndef HAVE_COCOA
408   if (goop->mode == transparent && plane_masks)
409     {
410       for (i = 0; i < goop->nlayers; i++)
411         goop->layers[i]->pixel = base_pixel | plane_masks[i];
412       goop->background = base_pixel;
413     }
414 # endif /* !HAVE_COCOA */
415
416   if (plane_masks)
417     free (plane_masks);
418
419 # ifndef HAVE_COCOA
420   if (goop->mode != transparent)
421 # endif /* !HAVE_COCOA */
422     {
423       XColor color;
424       color.flags = DoRed|DoGreen|DoBlue;
425
426       goop->background =
427         get_pixel_resource (dpy,cmap, "background", "Background");
428
429       for (i = 0; i < goop->nlayers; i++)
430         {
431           int H = random() % 360;                          /* range 0-360    */
432           double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
433           double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
434           hsv_to_rgb (H, S, V, &color.red, &color.green, &color.blue);
435           if (XAllocColor (dpy, cmap, &color))
436             goop->layers[i]->pixel = color.pixel;
437           else
438             goop->layers[i]->pixel =
439               WhitePixelOfScreen(DefaultScreenOfDisplay(dpy));
440 # ifdef HAVE_COCOA
441           if (goop->mode == transparent)
442             {
443               /* give a non-opaque alpha to the color */
444               unsigned long pixel = goop->layers[i]->pixel;
445               unsigned long amask = BlackPixelOfScreen (screen);
446               unsigned long a = (0xBBBBBBBB & amask);
447               pixel = (pixel & (~amask)) | a;
448               goop->layers[i]->pixel = pixel;
449             }
450 # endif /* HAVE_COCOA */
451         }
452     }
453
454   goop->pixmap = XCreatePixmap (dpy, window, width, height,
455                                 (goop->mode == xor ? 1L : depth));
456
457   gcv.background = goop->background;
458   gcv.foreground = get_pixel_resource (dpy, cmap, "foreground", "Foreground");
459   gcv.line_width = get_integer_resource (dpy, "thickness","Thickness");
460   goop->pixmap_gc = XCreateGC (dpy, goop->pixmap, GCLineWidth, &gcv);
461   goop->window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
462
463 # ifdef HAVE_COCOA
464   jwxyz_XSetAlphaAllowed (dpy, goop->pixmap_gc, True);
465 # endif /* HAVE_COCOA */
466   
467   return goop;
468 }
469
470 /* Well, the naming of this function is
471    confusing with goop_free()... */
472 static void
473 free_goop (struct goop *goop, Display *dpy)
474 {
475   int i;
476   for (i = 0; i < goop->nlayers; i++){
477     struct layer * layer = goop->layers[i];
478     free_layer(layer, dpy);
479   }
480   free(goop->layers);
481   XFreeGC(dpy, goop->pixmap_gc);
482   XFreeGC(dpy, goop->window_gc);
483 }
484
485 static void *
486 goop_init (Display *dpy, Window window)
487 {
488   XWindowAttributes xgwa;
489   XGetWindowAttributes (dpy, window, &xgwa);
490   return make_goop (xgwa.screen, xgwa.visual, window, xgwa.colormap,
491                     xgwa.width, xgwa.height, xgwa.depth);
492 }
493
494 static unsigned long
495 goop_draw (Display *dpy, Window window, void *closure)
496 {
497   struct goop *goop = (struct goop *) closure;
498   int i;
499
500   switch (goop->mode)
501     {
502 # ifndef HAVE_COCOA
503     case transparent:
504
505       for (i = 0; i < goop->nlayers; i++)
506         draw_layer_plane (dpy, goop->layers[i], goop->width, goop->height);
507
508       XSetForeground (dpy, goop->pixmap_gc, goop->background);
509       XSetFunction (dpy, goop->pixmap_gc, GXcopy);
510       XSetPlaneMask (dpy, goop->pixmap_gc, AllPlanes);
511       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
512                       goop->width, goop->height);
513
514       XSetForeground (dpy, goop->pixmap_gc, ~0L);
515
516       if (!goop->cmap_p && !goop->additive_p)
517         {
518           int j;
519           for (i = 0; i < goop->nlayers; i++)
520             for (j = 0; j < goop->layers[i]->nblobs; j++)
521               draw_blob (dpy, goop->pixmap, goop->pixmap_gc,
522                          goop->layers[i]->blobs[j], True);
523           XSetFunction (dpy, goop->pixmap_gc, GXclear);
524         }
525
526       for (i = 0; i < goop->nlayers; i++)
527         {
528           XSetPlaneMask (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
529           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
530                             goop->layers[i], goop->width, goop->height,
531                             True);
532         }
533       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
534                  goop->width, goop->height, 0, 0);
535       break;
536 #endif /* !HAVE_COCOA */
537
538     case xor:
539       XSetFunction (dpy, goop->pixmap_gc, GXcopy);
540       XSetForeground (dpy, goop->pixmap_gc, 0);
541       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
542                       goop->width, goop->height);
543       XSetFunction (dpy, goop->pixmap_gc, GXxor);
544       XSetForeground (dpy, goop->pixmap_gc, 1);
545       for (i = 0; i < goop->nlayers; i++)
546         draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
547                           goop->layers[i], goop->width, goop->height,
548                           (goop->mode != outline));
549       XCopyPlane (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
550                   goop->width, goop->height, 0, 0, 1L);
551       break;
552
553 # ifdef HAVE_COCOA
554     case transparent:
555 # endif
556     case opaque:
557     case outline:
558       XSetForeground (dpy, goop->pixmap_gc, goop->background);
559       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
560                       goop->width, goop->height);
561       for (i = 0; i < goop->nlayers; i++)
562         {
563           XSetForeground (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
564           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
565                             goop->layers[i], goop->width, goop->height,
566                             (goop->mode != outline));
567         }
568       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
569                  goop->width, goop->height, 0, 0);
570       break;
571
572     default:
573       abort ();
574       break;
575     }
576   return goop->delay;
577 }
578
579 static void
580 goop_reshape (Display *dpy, Window window, void *closure, 
581                  unsigned int w, unsigned int h)
582 {
583   struct goop *goop = (struct goop *) closure;
584
585   struct goop *goop2 = goop_init (dpy, window);
586   free_goop(goop, dpy);
587   memcpy (goop, goop2, sizeof(*goop));
588   free(goop2);
589 }
590
591 static Bool
592 goop_event (Display *dpy, Window window, void *closure, XEvent *event)
593 {
594   return False;
595 }
596
597 static void
598 goop_free (Display *dpy, Window window, void *closure)
599 {
600   struct goop *goop = (struct goop *) closure;
601   free_goop(goop, dpy);
602   free(goop);
603 }
604
605
606 \f
607
608 static const char *goop_defaults [] = {
609   ".background:         black",
610   ".foreground:         yellow",
611   "*delay:              12000",
612   "*additive:           true",
613   "*mode:               transparent",
614   "*count:              1",
615   "*planes:             12",
616   "*thickness:          5",
617   "*torque:             0.0075",
618   "*elasticity:         0.9",
619   "*maxVelocity:        0.5",
620 #ifdef USE_IPHONE
621   "*ignoreRotation:     True",
622 #endif
623   0
624 };
625
626 static XrmOptionDescRec goop_options [] = {
627   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
628   { "-count",           ".count",       XrmoptionSepArg, 0 },
629   { "-planes",          ".planes",      XrmoptionSepArg, 0 },
630   { "-mode",            ".mode",        XrmoptionSepArg, 0 },
631   { "-xor",             ".mode",        XrmoptionNoArg, "xor" },
632   { "-transparent",     ".mode",        XrmoptionNoArg, "transparent" },
633   { "-opaque",          ".mode",        XrmoptionNoArg, "opaque" },
634   { "-additive",        ".additive",    XrmoptionNoArg, "True" },
635   { "-subtractive",     ".additive",    XrmoptionNoArg, "false" },
636   { "-thickness",       ".thickness",   XrmoptionSepArg, 0 },
637   { "-torque",          ".torque",      XrmoptionSepArg, 0 },
638   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
639   { "-max-velocity",    ".maxVelocity", XrmoptionSepArg, 0 },
640   { 0, 0, 0, 0 }
641 };
642
643 XSCREENSAVER_MODULE ("Goop", goop)