http://ftp.x.org/contrib/applications/xscreensaver-2.23.tar.gz
[xscreensaver] / hacks / goop.c
1 /* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@netscape.com>
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
48 #define SCALE       10000  /* fixed-point math, for sub-pixel motion */
49 #define DEF_COUNT   12     /* When planes and count are 0, how many blobs. */
50
51
52 #define RAND(n) ((long) ((random() & 0x7fffffff) % ((long) (n))))
53 #define RANDSIGN() ((random() & 1) ? 1 : -1)
54
55 struct blob {
56   long x, y;            /* position of midpoint */
57   long dx, dy;          /* velocity and direction */
58   double torque;        /* rotational speed */
59   double th;            /* angle of rotation */
60   long elasticity;      /* how fast they deform */
61   long max_velocity;    /* speed limit */
62   long min_r, max_r;    /* radius range */
63   int npoints;          /* control points */
64   long *r;              /* radii */
65   spline *spline;
66 };
67
68 struct layer {
69   int nblobs;
70   struct blob **blobs;
71   Pixmap pixmap;
72   unsigned long pixel;
73   GC gc;
74 };
75
76 enum goop_mode {
77   transparent,
78   opaque,
79   xor,
80   outline
81 };
82
83 struct goop {
84   enum goop_mode mode;
85   int width, height;
86   int nlayers;
87   struct layer **layers;
88   unsigned long background;
89   Pixmap pixmap;
90   GC pixmap_gc;
91   GC window_gc;
92 };
93
94
95 static struct blob *
96 make_blob (int maxx, int maxy, int size)
97 {
98   struct blob *b = (struct blob *) calloc(1, sizeof(*b));
99   int i;
100   int mid;
101
102   maxx *= SCALE;
103   maxy *= SCALE;
104   size *= SCALE;
105
106   b->max_r = size/2;
107   b->min_r = size/10;
108
109   if (b->min_r < (5*SCALE)) b->min_r = (5*SCALE);
110   mid = ((b->min_r + b->max_r) / 2);
111
112   b->torque       = get_float_resource ("torque", "Torque");
113   b->elasticity   = SCALE * get_float_resource ("elasticity", "Elasticity");
114   b->max_velocity = SCALE * get_float_resource ("maxVelocity", "MaxVelocity");
115
116   b->x = RAND(maxx);
117   b->y = RAND(maxy);
118
119   b->dx = RAND(b->max_velocity) * RANDSIGN();
120   b->dy = RAND(b->max_velocity) * RANDSIGN();
121   b->th = frand(M_PI+M_PI) * RANDSIGN();
122   b->npoints = (random() % 5) + 5;
123
124   b->spline = make_spline (b->npoints);
125   b->r = (long *) malloc (sizeof(*b->r) * b->npoints);
126   for (i = 0; i < b->npoints; i++)
127     b->r[i] = ((random() % mid) + (mid/2)) * RANDSIGN();
128   return b;
129 }
130
131 static void 
132 throb_blob (struct blob *b)
133 {
134   int i;
135   double frac = ((M_PI+M_PI) / b->npoints);
136
137   for (i = 0; i < b->npoints; i++)
138     {
139       long r = b->r[i];
140       long ra = (r > 0 ? r : -r);
141       double th = (b->th > 0 ? b->th : -b->th);
142       long x, y;
143
144       /* place control points evenly around perimiter, shifted by theta */
145       x = b->x + ra * cos (i * frac + th);
146       y = b->y + ra * sin (i * frac + th);
147
148       b->spline->control_x[i] = x / SCALE;
149       b->spline->control_y[i] = y / SCALE;
150
151       /* alter the radius by a random amount, in the direction in which
152          it had been going (the sign of the radius indicates direction.) */
153       ra += (RAND(b->elasticity) * (r > 0 ? 1 : -1));
154       r = ra * (r >= 0 ? 1 : -1);
155
156       /* If we've reached the end (too long or too short) reverse direction. */
157       if ((ra > b->max_r && r >= 0) ||
158           (ra < b->min_r && r < 0))
159         r = -r;
160       /* And reverse direction in mid-course once every 50 times. */
161       else if (! (random() % 50))
162         r = -r;
163
164       b->r[i] = r;
165     }
166 }
167
168 static void
169 move_blob (struct blob *b, int maxx, int maxy)
170 {
171   maxx *= SCALE;
172   maxy *= SCALE;
173
174   b->x += b->dx;
175   b->y += b->dy;
176
177   /* If we've reached the edge of the box, reverse direction. */
178   if ((b->x > maxx && b->dx >= 0) ||
179       (b->x < 0    && b->dx < 0))
180     {
181       b->dx = -b->dx;
182     }
183   if ((b->y > maxy && b->dy >= 0) ||
184       (b->y < 0    && b->dy < 0))
185     {
186       b->dy = -b->dy;
187     }
188
189   /* Alter velocity randomly. */
190   if (! (random() % 10))
191     {
192       b->dx += (RAND(b->max_velocity/2) * RANDSIGN());
193       b->dy += (RAND(b->max_velocity/2) * RANDSIGN());
194
195       /* Throttle velocity */
196       if (b->dx > b->max_velocity || b->dx < -b->max_velocity)
197         b->dx /= 2;
198       if (b->dy > b->max_velocity || b->dy < -b->max_velocity)
199         b->dy /= 2;
200     }
201
202   {
203     double th = b->th;
204     double d = (b->torque == 0 ? 0 : frand(b->torque));
205     if (th < 0)
206       th = -(th + d);
207     else
208       th += d;
209
210     if (th > (M_PI+M_PI))
211       th -= (M_PI+M_PI);
212     else if (th < 0)
213       th += (M_PI+M_PI);
214
215     b->th = (b->th > 0 ? th : -th);
216   }
217
218   /* Alter direction of rotation randomly. */
219   if (! (random() % 100))
220     b->th *= -1;
221 }
222
223 static void
224 draw_blob (Display *dpy, Drawable drawable, GC gc, struct blob *b,
225            Bool fill_p)
226 {
227   compute_closed_spline (b->spline);
228 #ifdef DEBUG
229   {
230     int i;
231     for (i = 0; i < b->npoints; i++)
232       XDrawLine (dpy, drawable, gc, b->x/SCALE, b->y/SCALE,
233                  b->spline->control_x[i], b->spline->control_y[i]);
234   }
235 #else
236   if (fill_p)
237     XFillPolygon (dpy, drawable, gc, b->spline->points, b->spline->n_points,
238                   Nonconvex, CoordModeOrigin);
239   else
240 #endif
241     XDrawLines (dpy, drawable, gc, b->spline->points, b->spline->n_points,
242                 CoordModeOrigin);
243 }
244
245
246 static struct layer *
247 make_layer (Display *dpy, Window window, int width, int height, int nblobs)
248 {
249   int i;
250   struct layer *layer = (struct layer *) calloc(1, sizeof(*layer));
251   int blob_min, blob_max;
252   XGCValues gcv;
253   layer->nblobs = nblobs;
254
255   layer->blobs = (struct blob **) malloc(sizeof(*layer->blobs)*layer->nblobs);
256
257   blob_max = (width < height ? width : height) / 2;
258   blob_min = (blob_max * 2) / 3;
259   for (i = 0; i < layer->nblobs; i++)
260     layer->blobs[i] = make_blob (width, height,
261                                  (random() % (blob_max-blob_min)) + blob_min);
262
263   layer->pixmap = XCreatePixmap (dpy, window, width, height, 1);
264   layer->gc = XCreateGC (dpy, layer->pixmap, 0, &gcv);
265
266   return layer;
267 }
268
269 static void
270 draw_layer_plane (Display *dpy, struct layer *layer, int width, int height)
271 {
272   int i;
273   XSetForeground (dpy, layer->gc, 1L);
274   XFillRectangle (dpy, layer->pixmap, layer->gc, 0, 0, width, height);
275   XSetForeground (dpy, layer->gc, 0L);
276   for (i = 0; i < layer->nblobs; i++)
277     {
278       throb_blob (layer->blobs[i]);
279       move_blob (layer->blobs[i], width, height);
280       draw_blob (dpy, layer->pixmap, layer->gc, layer->blobs[i], True);
281     }
282 }
283
284
285 static void
286 draw_layer_blobs (Display *dpy, Drawable drawable, GC gc,
287                   struct layer *layer, int width, int height,
288                   Bool fill_p)
289 {
290   int i;
291   for (i = 0; i < layer->nblobs; i++)
292     {
293       throb_blob (layer->blobs[i]);
294       move_blob (layer->blobs[i], width, height);
295       draw_blob (dpy, drawable, gc, layer->blobs[i], fill_p);
296     }
297 }
298
299
300 static struct goop *
301 make_goop (Display *dpy, Window window, Colormap cmap,
302            int width, int height, long depth)
303 {
304   int i;
305   struct goop *goop = (struct goop *) calloc(1, sizeof(*goop));
306   XGCValues gcv;
307   int nblobs = get_integer_resource ("count", "Count");
308
309   unsigned long *plane_masks = 0;
310   unsigned long base_pixel = 0;
311
312   goop->mode = (get_boolean_resource("xor", "Xor")
313                 ? xor
314                 : (get_boolean_resource("transparent", "Transparent")
315                    ? transparent
316                    : opaque));
317
318   goop->width = width;
319   goop->height = height;
320
321
322   goop->nlayers = get_integer_resource ("planes", "Planes");
323   if (goop->nlayers <= 0)
324     goop->nlayers = (random() % (depth-2)) + 2;
325   goop->layers = (struct layer **) malloc(sizeof(*goop->layers)*goop->nlayers);
326
327
328   if (mono_p && goop->mode == transparent)
329     goop->mode = opaque;
330
331   /* Try to allocate some color planes before committing to nlayers.
332    */
333   if (goop->mode == transparent)
334     {
335       Bool additive_p = get_boolean_resource ("additive", "Additive");
336       int nplanes = goop->nlayers;
337       allocate_alpha_colors (dpy, cmap, &nplanes, additive_p, &plane_masks,
338                              &base_pixel);
339       if (nplanes > 1)
340         goop->nlayers = nplanes;
341       else
342         {
343           fprintf (stderr,
344          "%s: couldn't allocate any color planes; turning transparency off.\n",
345                    progname);
346           goop->mode = opaque;
347         }
348     }
349
350   {
351     int lblobs[32];
352     int total = DEF_COUNT;
353     memset (lblobs, 0, sizeof(lblobs));
354     if (nblobs <= 0)
355       while (total)
356         for (i = 0; total && i < goop->nlayers; i++)
357           lblobs[i]++, total--;
358     for (i = 0; i < goop->nlayers; i++)
359       goop->layers[i] = make_layer (dpy, window, width, height, 
360                                     (nblobs > 0 ? nblobs : lblobs[i]));
361   }
362
363   if (goop->mode == transparent && plane_masks)
364     {
365       for (i = 0; i < goop->nlayers; i++)
366         goop->layers[i]->pixel = base_pixel | plane_masks[i];
367       goop->background = base_pixel;
368     }
369   if (plane_masks)
370     free (plane_masks);
371
372   if (goop->mode != transparent)
373     {
374       XColor color;
375       color.flags = DoRed|DoGreen|DoBlue;
376
377       goop->background =
378         get_pixel_resource ("background", "Background", dpy,cmap);
379
380       for (i = 0; i < goop->nlayers; i++)
381         {
382           int H = random() % 360;                          /* range 0-360    */
383           double S = ((double) (random()%70) + 30)/100.0;  /* range 30%-100% */
384           double V = ((double) (random()%34) + 66)/100.0;  /* range 66%-100% */
385           hsv_to_rgb (H, S, V, &color.red, &color.green, &color.blue);
386           if (XAllocColor (dpy, cmap, &color))
387             goop->layers[i]->pixel = color.pixel;
388           else
389             goop->layers[i]->pixel =
390               WhitePixelOfScreen(DefaultScreenOfDisplay(dpy));
391         }
392     }
393
394   goop->pixmap = XCreatePixmap (dpy, window, width, height,
395                                 (goop->mode == xor ? 1L : depth));
396
397   gcv.background = goop->background;
398   gcv.foreground = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
399   gcv.line_width = get_integer_resource ("thickness","Thickness");
400   goop->pixmap_gc = XCreateGC (dpy, goop->pixmap, GCLineWidth, &gcv);
401   goop->window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv);
402
403   return goop;
404 }
405
406 static struct goop *
407 init_goop (Display *dpy, Window window)
408 {
409   XWindowAttributes xgwa;
410   XGetWindowAttributes (dpy, window, &xgwa);
411
412   return make_goop (dpy, window, xgwa.colormap,
413                     xgwa.width, xgwa.height, xgwa.depth);
414 }
415
416 static void
417 run_goop (Display *dpy, Window window, struct goop *goop)
418 {
419   int i;
420
421   switch (goop->mode)
422     {
423     case transparent:
424
425       for (i = 0; i < goop->nlayers; i++)
426         draw_layer_plane (dpy, goop->layers[i], goop->width, goop->height);
427
428       XSetForeground (dpy, goop->pixmap_gc, goop->background);
429       XSetPlaneMask (dpy, goop->pixmap_gc, AllPlanes);
430       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
431                       goop->width, goop->height);
432       XSetForeground (dpy, goop->pixmap_gc, ~0L);
433       for (i = 0; i < goop->nlayers; i++)
434         {
435           XSetPlaneMask (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
436 /*
437           XSetForeground (dpy, goop->pixmap_gc, ~0L);
438           XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
439                           goop->width, goop->height);
440           XSetForeground (dpy, goop->pixmap_gc, 0L);
441  */
442           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
443                             goop->layers[i], goop->width, goop->height,
444                             True);
445         }
446       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
447                  goop->width, goop->height, 0, 0);
448       break;
449
450     case xor:
451       XSetFunction (dpy, goop->pixmap_gc, GXcopy);
452       XSetForeground (dpy, goop->pixmap_gc, 0);
453       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
454                       goop->width, goop->height);
455       XSetFunction (dpy, goop->pixmap_gc, GXxor);
456       XSetForeground (dpy, goop->pixmap_gc, 1);
457       for (i = 0; i < goop->nlayers; i++)
458         draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
459                           goop->layers[i], goop->width, goop->height,
460                           (goop->mode != outline));
461       XCopyPlane (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
462                   goop->width, goop->height, 0, 0, 1L);
463       break;
464
465     case opaque:
466     case outline:
467       XSetForeground (dpy, goop->pixmap_gc, goop->background);
468       XFillRectangle (dpy, goop->pixmap, goop->pixmap_gc, 0, 0,
469                       goop->width, goop->height);
470       for (i = 0; i < goop->nlayers; i++)
471         {
472           XSetForeground (dpy, goop->pixmap_gc, goop->layers[i]->pixel);
473           draw_layer_blobs (dpy, goop->pixmap, goop->pixmap_gc,
474                             goop->layers[i], goop->width, goop->height,
475                             (goop->mode != outline));
476         }
477       XCopyArea (dpy, goop->pixmap, window, goop->window_gc, 0, 0,
478                  goop->width, goop->height, 0, 0);
479       break;
480
481     default:
482       abort ();
483       break;
484     }
485 }
486
487 \f
488 char *progclass = "Goop";
489
490 char *defaults [] = {
491   ".background:         black",
492   ".foreground:         white",
493   "*delay:              12000",
494   "*transparent:        true",
495   "*additive:           true",
496   "*xor:                false",
497   "*count:              0",
498   "*planes:             0",
499   "*thickness:          5",
500   "*torque:             0.0075",
501   "*elasticity:         1.8",
502   "*maxVelocity:        1.2",
503   0
504 };
505
506 XrmOptionDescRec options [] = {
507   { "-delay",           ".delay",       XrmoptionSepArg, 0 },
508   { "-count",           ".count",       XrmoptionSepArg, 0 },
509   { "-planes",          ".planes",      XrmoptionSepArg, 0 },
510   { "-transparent",     ".transparent", XrmoptionNoArg, "True" },
511   { "-non-transparent", ".transparent", XrmoptionNoArg, "False" },
512   { "-additive",        ".additive",    XrmoptionNoArg, "True" },
513   { "-subtractive",     ".additive",    XrmoptionNoArg, "false" },
514   { "-xor",             ".xor",         XrmoptionNoArg, "true" },
515   { "-no-xor",          ".xor",         XrmoptionNoArg, "false" },
516   { "-thickness",       ".thickness",   XrmoptionSepArg, 0 },
517   { "-torque",          ".torque",      XrmoptionSepArg, 0 },
518   { "-elasticity",      ".elasticity",  XrmoptionSepArg, 0 },
519   { "-max-velocity",    ".maxVelocity", XrmoptionSepArg, 0 },
520   { 0, 0, 0, 0 }
521 };
522
523 void
524 screenhack (Display *dpy, Window window)
525 {
526   struct goop *g = init_goop (dpy, window);
527   int delay = get_integer_resource ("delay", "Integer");
528   while (1)
529     {
530       run_goop (dpy, window, g);
531       XSync (dpy, True);
532       if (delay) usleep (delay);
533     }
534 }