/* lmorph, Copyright (c) 1993-1999 Sverre H. Huseby and Glenn T. Lines
*
* 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.
*/
/*------------------------------------------------------------------------
|
| FILE lmorph.c
| MODULE OF xscreensaver
|
| DESCRIPTION Smooth and non-linear morphing between 1D curves.
|
| WRITTEN BY Sverre H. Huseby Glenn T. Lines
| Kurvn. 30 Østgaardsgt. 5
| N-0495 Oslo N-0474 Oslo
| Norway Norway
|
| Phone: +47 901 63 579 Phone: +47 22 04 67 28
| E-mail: sverrehu@online.no E-mail: glennli@ifi.uio.no
| URL: http://home.sol.no/~sverrehu/
|
| The original idea, and the bilinear interpolation
| mathematics used, emerged in the head of the wise
| Glenn T. Lines.
|
| MODIFICATIONS october 1999 (shh)
| * Removed option to use integer arithmetic.
| * Increased default number of points, and brightened
| the foreground color a little bit.
| * Minor code cleanup (very minor, that is).
| * Default number of steps is no longer random.
| * Added -linewidth option (and resource).
|
| october 1999 (gtl)
| * Added cubic interpolation between shapes
| * Added non-linear transformation speed
|
| june 1998 (shh)
| * Minor code cleanup.
|
| january 1997 (shh)
| * Some code reformatting.
| * Added possibility to use float arithmetic.
| * Added -figtype option.
| * Made color blue default.
|
| december 1995 (jwz)
| * Function headers converted from ANSI to K&R.
| * Added posibility for random number of steps, and
| made this the default.
|
| march 1995 (shh)
| * Converted from an MS-Windows program to X Window.
|
| november 1993 (gtl, shh, lots of beer)
| * Original Windows version (we didn't know better).
+----------------------------------------------------------------------*/
#include <math.h>
#include "screenhack.h"
/*-----------------------------------------------------------------------+
| PRIVATE DATA |
+-----------------------------------------------------------------------*/
/* define MARGINS to make some space around the figure. */
#define MARGINS
#define MAXFIGS 20
#define TWO_PI (2.0 * M_PI)
#define RND(x) (random() % (x))
#define FT_OPEN 1
#define FT_CLOSED 2
#define FT_ALL (FT_OPEN | FT_CLOSED)
struct state {
Display *dpy;
Window window;
int numFigs; /* number of figure arrays. */
int numPoints; /* number of points in each array. */
int nWork; /* current work array number. */
int nFrom; /* current from array number. */
int nTo; /* current to array number. */
int nNext; /* current next array number (after to).*/
int shift; /* shifts the starting point of a figure */
int figType;
long delay; /* usecs to wait between updates. */
XPoint *aWork[2]; /* working arrays. */
XPoint *a[MAXFIGS]; /* the figure arrays. */
XPoint *aTmp; /* used as source when interrupting morph */
XPoint *aPrev; /* previous points displayed. */
XPoint *aCurr; /* the current points displayed. */
XPoint *aFrom; /* figure converting from. */
XPoint *aTo; /* figure converting to. */
XPoint *aNext; /* figure converting to next time. */
XPoint *aSlopeFrom; /* slope at start of morph */
XPoint *aSlopeTo; /* slope at end of morph */
int scrWidth, scrHeight;
double currGamma, maxGamma, deltaGamma;
GC gcDraw, gcClear;
};
/*-----------------------------------------------------------------------+
| PUBLIC DATA |
+-----------------------------------------------------------------------*/
static const char *lmorph_defaults [] = {
".background: black",
".foreground: #4444FF",
"*points: 200",
"*steps: 150",
"*delay: 70000",
"*figtype: all",
"*linewidth: 5",
0
};
static XrmOptionDescRec lmorph_options [] = {
{ "-points", ".points", XrmoptionSepArg, 0 },
{ "-steps", ".steps", XrmoptionSepArg, 0 },
{ "-delay", ".delay", XrmoptionSepArg, 0 },
{ "-figtype", ".figtype", XrmoptionSepArg, 0 },
{ "-linewidth", ".linewidth", XrmoptionSepArg, 0 },
{ 0, 0, 0, 0 }
};
/*-----------------------------------------------------------------------+
| PRIVATE FUNCTIONS |
+-----------------------------------------------------------------------*/
static void *
xmalloc(size_t size)
{
void *ret;
if ((ret = malloc(size)) == NULL) {
fprintf(stderr, "lmorph: out of memory\n");
exit(1);
}
return ret;
}
static void
initPointArrays(struct state *st)
{
int q, w;
int mx, my; /* max screen coordinates. */
int mp; /* max point number. */
int s, rx, ry;
int marginx, marginy;
double scalex, scaley;
mx = st->scrWidth - 1;
my = st->scrHeight - 1;
mp = st->numPoints - 1;
st->aWork[0] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
st->aWork[1] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
st->aTmp = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
if (st->figType & FT_CLOSED) {
/* rectangle */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
s = st->numPoints / 4;
for (q = 0; q < s; q++) {
st->a[st->numFigs][q].x = ((double) q / s) * mx;
st->a[st->numFigs][q].y = 0;
st->a[st->numFigs][s + q].x = mx;
st->a[st->numFigs][s + q].y = ((double) q / s) * my;
st->a[st->numFigs][2 * s + q].x = mx - ((double) q / s) * mx;
st->a[st->numFigs][2 * s + q].y = my;
st->a[st->numFigs][3 * s + q].x = 0;
st->a[st->numFigs][3 * s + q].y = my - ((double) q / s) * my;
}
for (q = 4 * s; q < st->numPoints; q++)
st->a[st->numFigs][q].x = st->a[st->numFigs][q].y = 0;
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + rx * sin(1 * TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * cos(3 * TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + ry * sin(3 * TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * cos(1 * TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + ry
* (0.8 - 0.2 * sin(30 * TWO_PI * q / mp))
* sin(TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry
* (0.8 - 0.2 * sin(30 * TWO_PI * q / mp))
* cos(TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + ry * sin(TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * cos(TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + rx * cos(TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * sin(TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + rx * sin(2 * TWO_PI * (double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * cos(3 * TWO_PI * (double) q / mp);
}
st->a[st->numFigs][mp].x = st->a[st->numFigs][0].x;
st->a[st->numFigs][mp].y = st->a[st->numFigs][0].y;
++st->numFigs;
}
if (st->figType & FT_OPEN) {
/* sine wave, one period */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = ((double) q / st->numPoints) * mx;
st->a[st->numFigs][q].y = (1.0 - sin(((double) q / mp) * TWO_PI))
* my / 2.0;
}
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = ((double) q / mp) * mx;
st->a[st->numFigs][q].y = (1.0 - cos(((double) q / mp) * 3 * TWO_PI))
* my / 2.0;
}
++st->numFigs;
/* spiral, one endpoint at bottom */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + ry * sin(5 * TWO_PI * (double) q / mp)
* ((double) q / mp);
st->a[st->numFigs][q].y = my / 2 + ry * cos(5 * TWO_PI * (double) q / mp)
* ((double) q / mp);
}
++st->numFigs;
/* spiral, one endpoint at top */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
rx = mx / 2;
ry = my / 2;
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = mx / 2 + ry * sin(6 * TWO_PI * (double) q / mp)
* ((double) q / mp);
st->a[st->numFigs][q].y = my / 2 - ry * cos(6 * TWO_PI * (double) q / mp)
* ((double) q / mp);
}
++st->numFigs;
/* */
st->a[st->numFigs] = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
for (q = 0; q < st->numPoints; q++) {
st->a[st->numFigs][q].x = ((double) q / mp) * mx;
st->a[st->numFigs][q].y = (1.0 - sin(((double) q / mp) * 5 * TWO_PI))
* my / 2.0;
}
++st->numFigs;
}
#ifdef MARGINS
/* make some space around the figures. */
marginx = (mx + 1) / 10;
marginy = (my + 1) / 10;
scalex = (double) ((mx + 1) - 2.0 * marginx) / (mx + 1.0);
scaley = (double) ((my + 1) - 2.0 * marginy) / (my + 1.0);
for (q = 0; q < st->numFigs; q++)
for (w = 0; w < st->numPoints; w++) {
st->a[q][w].x = marginx + st->a[q][w].x * scalex;
st->a[q][w].y = marginy + st->a[q][w].y * scaley;
}
#endif
}
static void
initLMorph(struct state *st)
{
int steps;
XGCValues gcv;
XWindowAttributes wa;
Colormap cmap;
char *ft;
int i;
st->maxGamma = 1.0;
st->numPoints = get_integer_resource(st->dpy, "points", "Integer");
steps = get_integer_resource(st->dpy, "steps", "Integer");
st->delay = get_integer_resource(st->dpy, "delay", "Integer");
ft = get_string_resource(st->dpy, "figtype", "String");
if (strcmp(ft, "all") == 0)
st->figType = FT_ALL;
else if (strcmp(ft, "open") == 0)
st->figType = FT_OPEN;
else if (strcmp(ft, "closed") == 0)
st->figType = FT_CLOSED;
else {
fprintf(stderr, "figtype should be `all', `open' or `closed'.\n");
st->figType = FT_ALL;
}
if (ft) free (ft);
if (steps <= 0)
steps = (random() % 400) + 100;
st->deltaGamma = 1.0 / steps;
XGetWindowAttributes(st->dpy, st->window, &wa);
st->scrWidth = wa.width;
st->scrHeight = wa.height;
cmap = wa.colormap;
gcv.foreground = get_pixel_resource(st->dpy, cmap, "foreground", "Foreground");
st->gcDraw = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
XSetForeground(st->dpy, st->gcDraw, gcv.foreground);
gcv.foreground = get_pixel_resource(st->dpy, cmap, "background", "Background");
st->gcClear = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
XClearWindow(st->dpy, st->window);
initPointArrays(st);
st->aCurr = st->aWork[st->nWork = 0];
st->aPrev = NULL;
st->currGamma = st->maxGamma + 1.0; /* force creation of new figure at startup */
st->nTo = RND(st->numFigs);
do {
st->nNext = RND(st->numFigs);
} while (st->nNext == st->nTo);
st->aSlopeTo = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
st->aSlopeFrom = (XPoint *) xmalloc(st->numPoints * sizeof(XPoint));
st->aNext = 0;
for (i = 0; i < st->numPoints ; i++) {
st->aSlopeTo[i].x = 0.0;
st->aSlopeTo[i].y = 0.0;
}
{ /* jwz for version 2.11 */
/* int width = random() % 10;*/
int width = get_integer_resource(st->dpy, "linewidth", "Integer");
int style = LineSolid;
int cap = (width > 1 ? CapRound : CapButt);
int join = (width > 1 ? JoinRound : JoinBevel);
if (width == 1)
width = 0;
XSetLineAttributes(st->dpy, st->gcDraw, width, style, cap, join);
XSetLineAttributes(st->dpy, st->gcClear, width, style, cap, join);
}
}
/* 55% of execution time */
static void
createPoints(struct state *st)
{
int q;
XPoint *pa = st->aCurr, *pa1 = st->aFrom, *pa2 = st->aTo;
XPoint *qa1 = st->aSlopeFrom, *qa2 = st->aSlopeTo;
float fg, f1g;
float speed;
fg = st->currGamma;
f1g = 1.0 - st->currGamma;
for (q = st->numPoints; q; q--) {
speed = 0.45 * sin(TWO_PI * (double) (q + st->shift) / (st->numPoints - 1));
fg = st->currGamma + 1.67 * speed
* exp(-200.0 * (st->currGamma - 0.5 + 0.7 * speed)
* (st->currGamma - 0.5 + 0.7 * speed));
f1g = 1.0 - fg;
pa->x = (short) (f1g * f1g * f1g * pa1->x + f1g * f1g * fg
* (3 * pa1->x + qa1->x) + f1g * fg * fg
* (3 * pa2->x - qa2->x) + fg * fg * fg * pa2->x);
pa->y = (short) (f1g * f1g * f1g * pa1->y + f1g * f1g * fg
* (3 * pa1->y + qa1->y) + f1g * fg * fg
* (3 * pa2->y - qa2->y) + fg * fg * fg * pa2->y);
++pa;
++pa1;
++pa2;
++qa1;
++qa2;
}
}
/* 36% of execution time */
static void
drawImage(struct state *st)
{
#if 0
int q;
XPoint *old0, *old1, *new0, *new1;
/* Problem: update the window without too much flickering. I do
* this by handling each linesegment separately. First remove a
* line, then draw the new line. The problem is that this leaves
* small black pixels on the figure. To fix this, we draw the
* entire figure using XDrawLines() afterwards. */
if (st->aPrev) {
old0 = st->aPrev;
old1 = st->aPrev + 1;
new0 = st->aCurr;
new1 = st->aCurr + 1;
for (q = st->numPoints - 1; q; q--) {
XDrawLine(st->dpy, st->window, st->gcClear,
old0->x, old0->y, old1->x, old1->y);
XDrawLine(st->dpy, st->window, st->gcDraw,
new0->x, new0->y, new1->x, new1->y);
++old0;
++old1;
++new0;
++new1;
}
}
#else
XClearWindow(st->dpy,st->window);
#endif
XDrawLines(st->dpy, st->window, st->gcDraw, st->aCurr, st->numPoints, CoordModeOrigin);
}
/* neglectible % of execution time */
static void
animateLMorph(struct state *st)
{
int i;
if (st->currGamma > st->maxGamma) {
st->currGamma = 0.0;
st->nFrom = st->nTo;
st->nTo = st->nNext;
st->aFrom = st->a[st->nFrom];
st->aTo = st->a[st->nTo];
do {
st->nNext = RND(st->numFigs);
} while (st->nNext == st->nTo);
st->aNext = st->a[st->nNext];
st->shift = RND(st->numPoints);
if (RND(2)) {
/* reverse the array to get more variation. */
int i1, i2;
XPoint p;
for (i1 = 0, i2 = st->numPoints - 1; i1 < st->numPoints / 2; i1++, i2--) {
p = st->aNext[i1];
st->aNext[i1] = st->aNext[i2];
st->aNext[i2] = p;
}
}
/* calculate the slopes */
for (i = 0; i < st->numPoints ; i++) {
st->aSlopeFrom[i].x = st->aSlopeTo[i].x;
st->aSlopeFrom[i].y = st->aSlopeTo[i].y;
st->aSlopeTo[i].x = st->aNext[i].x - st->aTo[i].x;
st->aSlopeTo[i].y = (st->aNext[i].y - st->aTo[i].y);
}
}
createPoints(st);
drawImage(st);
st->aPrev = st->aCurr;
st->aCurr = st->aWork[st->nWork ^= 1];
st->currGamma += st->deltaGamma;
}
/*-----------------------------------------------------------------------+
| PUBLIC FUNCTIONS |
+-----------------------------------------------------------------------*/
static void *
lmorph_init (Display *d, Window w)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
st->dpy = d;
st->window = w;
initLMorph(st);
return st;
}
static unsigned long
lmorph_draw (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
animateLMorph(st);
return st->delay;
}
static void
lmorph_reshape (Display *dpy, Window window, void *closure,
unsigned int w, unsigned int h)
{
}
static Bool
lmorph_event (Display *dpy, Window window, void *closure, XEvent *event)
{
return False;
}
static void
lmorph_free (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
int i;
XFreeGC (dpy, st->gcDraw);
XFreeGC (dpy, st->gcClear);
free (st->aWork[0]);
free (st->aWork[1]);
free (st->aTmp);
free (st->aSlopeTo);
free (st->aSlopeFrom);
for (i = 0; i < MAXFIGS; i++)
if (st->a[i]) free (st->a[i]);
free (st);
}
XSCREENSAVER_MODULE ("LMorph", lmorph)