/* -*- Mode: C; tab-width: 4 -*- */ /* goop --- goop from a lava lamp */ #if !defined( lint ) && !defined( SABER ) static const char sccsid[] = "@(#)goop.c 5.00 2000/11/01 xlockmore"; #endif /*- * Copyright (c) 1997 by Jamie Zawinski * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, * provided that the above copyright notice appear in all copies and that * both that copyright notice and this permission notice appear in * supporting documentation. * * This file is provided AS IS with no warranties of any kind. The author * shall have no liability with respect to the infringement of copyrights, * trade secrets or any patents by this file or any part thereof. In no * event will the author be liable for any lost revenue or profits or * other special, indirect and consequential damages. * * Revision History: * 01-Nov-2000: Allocation checks * 24-Mar-1998: xlock version David Bagley * 1997: xscreensaver version Jamie Zawinski */ /*- * original copyright * xscreensaver, Copyright (c) 1997 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. */ /*- * 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... */ #ifdef STANDALONE #define MODE_goop #define PROGCLASS "Goop" #define HACK_INIT init_goop #define HACK_DRAW draw_goop #define goop_opts xlockmore_opts #define DEFAULTS "*delay: 40000 \n" \ "*count: 100 \n" /*- Come back to this. "*delay: 12000", "*transparent: true", "*additive: true", "*xor: false", "*count: 0", "*planes: 0", "*thickness: 5", "*torque: 0.0075", "*elasticity: 1.8", "*maxVelocity: 1.2", */ #include "xlockmore.h" /* in xscreensaver distribution */ #else /* STANDALONE */ #include "xlock.h" /* in xlockmore distribution */ #endif /* STANDALONE */ #include #ifdef MODE_goop ModeSpecOpt goop_opts = {0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL}; #ifdef USE_MODULES ModStruct goop_description = {"goop", "init_goop", "draw_goop", "release_goop", "init_goop", "init_goop", (char *) NULL, &goop_opts, 10000, -12, 1, 1, 64, 1.0, "", "Shows goop from a lava lamp", 0, NULL}; #endif #define SCALE 10000 /* fixed-point math, for sub-pixel motion */ #define DEF_COUNT 12 /* When planes and count are 0, how many blobs. */ #define RANDSIGN() ((LRAND() & 1) ? 1 : -1) typedef struct { 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 *splines; } blob; typedef struct { int nblobs; /* number of blops per plane */ blob *blobs; Pixmap pixmap; unsigned long pixel; GC gc; } layer; enum goop_mode { transparent, opaque, xored, outline }; typedef struct { enum goop_mode mode; int width, height; int nlayers; layer *layers; unsigned long background; Pixmap pixmap; GC pixmap_gc; } goopstruct; static goopstruct *goops = (goopstruct *) NULL; static Bool make_blob(blob * b, int maxx, int maxy, int size) { int i; long 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 = 0.0075; /* torque init */ b->elasticity = (long) (SCALE * 1.8); /* elasticity init */ b->max_velocity = (long) (SCALE * 1.2); /* max_velocity init */ b->x = NRAND(maxx); b->y = NRAND(maxy); b->dx = NRAND(b->max_velocity) * RANDSIGN(); b->dy = NRAND(b->max_velocity) * RANDSIGN(); b->th = (2.0 * M_PI) * LRAND() / MAXRAND * RANDSIGN(); b->npoints = (int) (LRAND() % 5) + 5; b->splines = make_spline(b->npoints); if ((b->r = (long *) malloc(sizeof (*b->r) * b->npoints)) == NULL) return False; for (i = 0; i < b->npoints; i++) b->r[i] = ((LRAND() % mid) + (mid / 2)) * RANDSIGN(); return True; } static void throb_blob(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 + (long) (ra * cos(i * frac + th)); y = b->y + (long) (ra * sin(i * frac + th)); b->splines->control_x[i] = x / SCALE; b->splines->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 += (NRAND(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 (!(LRAND() % 50)) r = -r; b->r[i] = r; } } static void move_blob(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 (!(LRAND() % 10)) { b->dx += (NRAND(b->max_velocity / 2) * RANDSIGN()); b->dy += (NRAND(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 : (b->torque) * LRAND() / MAXRAND); 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 (!(LRAND() % 100)) b->th *= -1; } static void draw_blob(Display * display, Drawable drawable, GC gc, blob * b, Bool fill_p) { compute_closed_spline(b->splines); #ifdef DEBUG { int i; for (i = 0; i < b->npoints; i++) XDrawLine(display, drawable, gc, b->x / SCALE, b->y / SCALE, b->splines->control_x[i], b->splines->control_y[i]); } #else if (fill_p) XFillPolygon(display, drawable, gc, b->splines->points, b->splines->n_points, Nonconvex, CoordModeOrigin); else #endif XDrawLines(display, drawable, gc, b->splines->points, b->splines->n_points, CoordModeOrigin); } static Bool make_layer(ModeInfo * mi, layer * l, int nblobs) { int i; int blob_min, blob_max; XGCValues gcv; int width = MI_WIDTH(mi), height = MI_HEIGHT(mi); l->nblobs = nblobs; if ((l->blobs = (blob *) calloc(l->nblobs, sizeof (blob))) == NULL) return False; blob_max = (width < height ? width : height) / 2; blob_min = (blob_max * 2) / 3; for (i = 0; i < l->nblobs; i++) if (!make_blob(&(l->blobs[i]), width, height, (int) (LRAND() % (blob_max - blob_min + 1)) + blob_min)) return False; if ((l->pixmap = XCreatePixmap(MI_DISPLAY(mi), MI_WINDOW(mi), width, height, 1)) == None) return False; if ((l->gc = XCreateGC(MI_DISPLAY(mi), l->pixmap, 0, &gcv)) == None) return False; return True; } static void draw_layer_plane(Display * display, layer * layer_plane, int width, int height) { int i; XSetForeground(display, layer_plane->gc, 1L); XFillRectangle(display, layer_plane->pixmap, layer_plane->gc, 0, 0, width, height); XSetForeground(display, layer_plane->gc, 0L); for (i = 0; i < layer_plane->nblobs; i++) { throb_blob(&(layer_plane->blobs[i])); move_blob(&(layer_plane->blobs[i]), width, height); draw_blob(display, layer_plane->pixmap, layer_plane->gc, &(layer_plane->blobs[i]), True); } } static void draw_layer_blobs(Display * display, Drawable drawable, GC gc, layer * layer_plane, int width, int height, Bool fill_p) { int i; for (i = 0; i < layer_plane->nblobs; i++) { throb_blob(&(layer_plane->blobs[i])); move_blob(&(layer_plane->blobs[i]), width, height); draw_blob(display, drawable, gc, &(layer_plane->blobs[i]), fill_p); } } static void free_goop(Display * display, goopstruct * gp) { int l; if (gp->layers != NULL) { for (l = 0; l < gp->nlayers; l++) { if (gp->layers[l].blobs != NULL) { int b; for (b = 0; b < gp->layers[l].nblobs; b++) { if (gp->layers[l].blobs[b].r != NULL) free(gp->layers[l].blobs[b].r); free_spline(gp->layers[l].blobs[b].splines); } free(gp->layers[l].blobs); } if (gp->layers[l].gc != None) XFreeGC(display, gp->layers[l].gc); if (gp->layers[l].pixmap != None) XFreePixmap(display, gp->layers[l].pixmap); } free(gp->layers); gp->layers = (layer *) NULL; } if (gp->pixmap_gc != None) { XFreeGC(display, gp->pixmap_gc); gp->pixmap_gc = None; } if (gp->pixmap != None) { XFreePixmap(display, gp->pixmap); gp->pixmap = None; } } void init_goop(ModeInfo * mi) { Display *display = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int i; XGCValues gcv; int nblobs; unsigned long *plane_masks = NULL; unsigned long base_pixel = 0; goopstruct *gp; if (goops == NULL) { if ((goops = (goopstruct *) calloc(MI_NUM_SCREENS(mi), sizeof (goopstruct))) == NULL) return; } gp = &goops[MI_SCREEN(mi)]; gp->mode = (False /* xor init */ ? xored : (True /* transparent init */ ? transparent : opaque)); gp->width = MI_WIDTH(mi); gp->height = MI_HEIGHT(mi); free_goop(display, gp); gp->nlayers = 0; /* planes init */ if (gp->nlayers <= 0) gp->nlayers = (int) (LRAND() % (MI_DEPTH(mi) - 2)) + 2; if ((gp->layers = (layer *) calloc(gp->nlayers, sizeof (layer))) == NULL) { return; /* free_goop just ran */ } if ((MI_NPIXELS(mi) < 2) && gp->mode == transparent) gp->mode = opaque; /* Try to allocate some color planes before committing to nlayers. */ #if 0 if (gp->mode == transparent) { Bool additive_p = True; /* additive init */ int nplanes = gp->nlayers; /* allocate_alpha_colors (display, MI_COLORMAP(mi), &nplanes, additive_p, &plane_masks, &base_pixel); *//* COME BACK */ if (nplanes > 1) gp->nlayers = nplanes; else { (void) fprintf(stderr, "could not allocate any color planes; turning transparency off.\n"); gp->mode = opaque; } } #else if (gp->mode == transparent) gp->mode = opaque; #endif nblobs = MI_COUNT(mi); if (nblobs < 0) { nblobs = NRAND(-nblobs) + 1; /* Add 1 so its not too boring */ } { int *lblobs; int total = DEF_COUNT; if ((lblobs = (int *) calloc(gp->nlayers, sizeof (int))) == NULL) { free_goop(display, gp); return; } if (nblobs <= 0) while (total) for (i = 0; total && i < gp->nlayers; i++) lblobs[i]++, total--; for (i = 0; i < gp->nlayers; i++) if (!make_layer(mi, &(gp->layers[i]), (nblobs > 0 ? nblobs : lblobs[i]))) free_goop(display, gp); free(lblobs); } if (gp->mode == transparent && plane_masks) { for (i = 0; i < gp->nlayers; i++) gp->layers[i].pixel = base_pixel | plane_masks[i]; gp->background = base_pixel; } if (plane_masks != NULL) free(plane_masks); if (gp->mode != transparent) { gp->background = 0; /* init */ for (i = 0; i < gp->nlayers; i++) { if (MI_NPIXELS(mi) > 2) gp->layers[i].pixel = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi))); else gp->layers[i].pixel = MI_WHITE_PIXEL(mi); } } if ((gp->pixmap = XCreatePixmap(display, window, MI_WIDTH(mi), MI_HEIGHT(mi), (gp->mode == xored ? 1 : MI_DEPTH(mi)))) == None) { free_goop(display, gp); return; } gcv.background = gp->background; gcv.foreground = 255; /* init */ gcv.line_width = 5; /* thickness init */ if ((gp->pixmap_gc = XCreateGC(display, gp->pixmap, GCLineWidth, &gcv)) == None) { free_goop(display, gp); return; } MI_CLEARWINDOW(mi); } void draw_goop(ModeInfo * mi) { Display *display = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int i; goopstruct *gp; if (goops == NULL) return; gp = &goops[MI_SCREEN(mi)]; if (gp->layers == NULL) return; MI_IS_DRAWN(mi) = True; switch (gp->mode) { case transparent: for (i = 0; i < gp->nlayers; i++) draw_layer_plane(display, &(gp->layers[i]), gp->width, gp->height); XSetForeground(display, gp->pixmap_gc, gp->background); XSetPlaneMask(display, gp->pixmap_gc, AllPlanes); XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0, gp->width, gp->height); XSetForeground(display, gp->pixmap_gc, ~0L); for (i = 0; i < gp->nlayers; i++) { XSetPlaneMask(display, gp->pixmap_gc, gp->layers[i].pixel); #if 0 XSetForeground (display, gp->pixmap_gc, ~0L); XFillRectangle (display, gp->pixmap, gp->pixmap_gc, 0, 0, gp->width, gp->height); XSetForeground (display, gp->pixmap_gc, 0L); #endif draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc, &(gp->layers[i]), gp->width, gp->height, True); } XCopyArea(display, gp->pixmap, window, MI_GC(mi), 0, 0, gp->width, gp->height, 0, 0); break; case xored: XSetFunction(display, gp->pixmap_gc, GXcopy); XSetForeground(display, gp->pixmap_gc, 0); XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0, gp->width, gp->height); XSetFunction(display, gp->pixmap_gc, GXxor); XSetForeground(display, gp->pixmap_gc, 1); for (i = 0; i < gp->nlayers; i++) draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc, &(gp->layers[i]), gp->width, gp->height, (gp->mode != outline)); XCopyPlane(display, gp->pixmap, window, MI_GC(mi), 0, 0, gp->width, gp->height, 0, 0, 1L); break; case opaque: case outline: XSetForeground(display, gp->pixmap_gc, MI_BLACK_PIXEL(mi)); XFillRectangle(display, gp->pixmap, gp->pixmap_gc, 0, 0, gp->width, gp->height); for (i = 0; i < gp->nlayers; i++) { XSetForeground(display, gp->pixmap_gc, gp->layers[i].pixel); draw_layer_blobs(display, gp->pixmap, gp->pixmap_gc, &(gp->layers[i]), gp->width, gp->height, (gp->mode != outline)); } XCopyArea(display, gp->pixmap, window, MI_GC(mi), 0, 0, gp->width, gp->height, 0, 0); break; default: if (MI_IS_VERBOSE(mi)) { (void) fprintf(stderr, "Weirdness in draw_goop()\n"); (void) fprintf(stderr, "gp->mode = %d\n", gp->mode); } break; } } void release_goop(ModeInfo * mi) { if (goops != NULL) { int screen; for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) free_goop(MI_DISPLAY(mi), &goops[screen]); free(goops); goops = (goopstruct *) NULL; } } #endif /* MODE_goop */