xenocara/app/xlockmore/modes/goop.c
2006-11-26 11:07:42 +00:00

619 lines
17 KiB
C

/* -*- 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 <bagleyd@tux.org>
* 1997: xscreensaver version Jamie Zawinski <jwz@jwz.org>
*/
/*-
* original copyright
* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz@jwz.org>
*
* 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 <spline.h>
#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 */