/* xscreensaver, Copyright (c) 1997-2008 Jamie Zawinski * * 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 #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; int delay; }; static struct blob * make_blob (Display *dpy, 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 (dpy, "torque", "Torque"); b->elasticity = SCALE * get_float_resource (dpy, "elasticity", "Elasticity"); b->max_velocity = SCALE * get_float_resource (dpy, "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] = (long) ((random() % mid) + (mid/2)) * RANDSIGN(); return b; } static void free_blob(struct blob *blob) { free_spline(blob->spline); free(blob->r); free(blob); } 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; if (width < 100 || height < 100) /* tiny window */ blob_max *= 10; blob_min = (blob_max * 2) / 3; for (i = 0; i < layer->nblobs; i++){ int j = blob_max - blob_min; layer->blobs[i] = make_blob (dpy, width, height, (j ? random() % j : 0) + blob_min); } layer->pixmap = XCreatePixmap (dpy, window, width, height, 1); layer->gc = XCreateGC (dpy, layer->pixmap, 0, &gcv); # ifdef HAVE_JWXYZ jwxyz_XSetAlphaAllowed (dpy, layer->gc, True); # endif /* HAVE_JWXYZ */ return layer; } static void free_layer(struct layer *layer, Display *dpy) { int i; for (i = 0; i < layer->nblobs; i++){ free_blob(layer->blobs[i]); } free(layer->blobs); XFreeGC(dpy, layer->gc); free(layer); } #ifndef HAVE_JWXYZ 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); } } #endif /* !HAVE_JWXYZ */ 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 (dpy, "count", "Count"); unsigned long *plane_masks = 0; # ifndef HAVE_JWXYZ unsigned long base_pixel = 0; # endif char *s; s = get_string_resource (dpy, "mode", "Mode"); goop->mode = transparent; if (!s || !*s || !strcasecmp (s, "transparent")) ; else if (!strcasecmp (s, "opaque")) goop->mode = opaque; else if (!strcasecmp (s, "xor")) goop->mode = xor; else fprintf (stderr, "%s: bogus mode: \"%s\"\n", progname, s); free(s); goop->delay = get_integer_resource (dpy, "delay", "Integer"); goop->width = width; goop->height = height; goop->nlayers = get_integer_resource (dpy, "planes", "Planes"); if (goop->nlayers <= 0) goop->nlayers = (random() % (depth-2)) + 2; if (! goop->layers) goop->layers = (struct layer **) malloc(sizeof(*goop->layers)*goop->nlayers); goop->additive_p = get_boolean_resource (dpy, "additive", "Additive"); goop->cmap_p = has_writable_cells (screen, visual); if (mono_p && goop->mode == transparent) goop->mode = opaque; # ifndef HAVE_JWXYZ /* 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; } } # endif /* !HAVE_JWXYZ */ { 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])); } # ifndef HAVE_JWXYZ 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; } # endif /* !HAVE_JWXYZ */ if (plane_masks) free (plane_masks); # ifndef HAVE_JWXYZ if (goop->mode != transparent) # endif /* !HAVE_JWXYZ */ { XColor color; color.flags = DoRed|DoGreen|DoBlue; goop->background = get_pixel_resource (dpy,cmap, "background", "Background"); 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)); # ifdef HAVE_JWXYZ if (goop->mode == transparent) { /* give a non-opaque alpha to the color */ unsigned long pixel = goop->layers[i]->pixel; unsigned long amask = BlackPixelOfScreen (screen); unsigned long a = (0xBBBBBBBB & amask); pixel = (pixel & (~amask)) | a; goop->layers[i]->pixel = pixel; } # endif /* HAVE_JWXYZ */ } } goop->pixmap = XCreatePixmap (dpy, window, width, height, (goop->mode == xor ? 1L : depth)); gcv.background = goop->background; gcv.foreground = get_pixel_resource (dpy, cmap, "foreground", "Foreground"); gcv.line_width = get_integer_resource (dpy, "thickness","Thickness"); goop->pixmap_gc = XCreateGC (dpy, goop->pixmap, GCLineWidth, &gcv); goop->window_gc = XCreateGC (dpy, window, GCForeground|GCBackground, &gcv); # ifdef HAVE_JWXYZ jwxyz_XSetAlphaAllowed (dpy, goop->pixmap_gc, True); # endif /* HAVE_JWXYZ */ return goop; } /* Well, the naming of this function is confusing with goop_free()... */ static void free_goop (struct goop *goop, Display *dpy) { int i; for (i = 0; i < goop->nlayers; i++){ struct layer * layer = goop->layers[i]; free_layer(layer, dpy); } free(goop->layers); XFreeGC(dpy, goop->pixmap_gc); XFreeGC(dpy, goop->window_gc); } static void * goop_init (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 unsigned long goop_draw (Display *dpy, Window window, void *closure) { struct goop *goop = (struct goop *) closure; int i; switch (goop->mode) { # ifndef HAVE_JWXYZ 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) { int j; 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; #endif /* !HAVE_JWXYZ */ 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; # ifdef HAVE_JWXYZ case transparent: # endif 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; } return goop->delay; } static void goop_reshape (Display *dpy, Window window, void *closure, unsigned int w, unsigned int h) { struct goop *goop = (struct goop *) closure; if (w != goop->width || h != goop->height) { struct goop *goop2 = goop_init (dpy, window); free_goop(goop, dpy); memcpy (goop, goop2, sizeof(*goop)); free(goop2); } } static Bool goop_event (Display *dpy, Window window, void *closure, XEvent *event) { return False; } static void goop_free (Display *dpy, Window window, void *closure) { struct goop *goop = (struct goop *) closure; free_goop(goop, dpy); free(goop); } static const char *goop_defaults [] = { ".background: black", ".foreground: yellow", "*delay: 12000", "*additive: true", "*mode: transparent", "*count: 1", "*planes: 12", "*thickness: 5", "*torque: 0.0075", "*elasticity: 0.9", "*maxVelocity: 0.5", #ifdef HAVE_MOBILE "*ignoreRotation: True", #endif 0 }; static XrmOptionDescRec goop_options [] = { { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-count", ".count", XrmoptionSepArg, 0 }, { "-planes", ".planes", XrmoptionSepArg, 0 }, { "-mode", ".mode", XrmoptionSepArg, 0 }, { "-xor", ".mode", XrmoptionNoArg, "xor" }, { "-transparent", ".mode", XrmoptionNoArg, "transparent" }, { "-opaque", ".mode", XrmoptionNoArg, "opaque" }, { "-additive", ".additive", XrmoptionNoArg, "True" }, { "-subtractive", ".additive", XrmoptionNoArg, "false" }, { "-thickness", ".thickness", XrmoptionSepArg, 0 }, { "-torque", ".torque", XrmoptionSepArg, 0 }, { "-elasticity", ".elasticity", XrmoptionSepArg, 0 }, { "-max-velocity", ".maxVelocity", XrmoptionSepArg, 0 }, { 0, 0, 0, 0 } }; XSCREENSAVER_MODULE ("Goop", goop)