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