1d6bee2b00bb8cf413a1d518dc764df3689b5c3e
[xscreensaver] / hacks / lmorph.c
1 /* lmorph, Copyright (c) 1993-1999 Sverre H. Huseby and Glenn T. Lines
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 /*------------------------------------------------------------------------
13  |
14  |  FILE            lmorph.c
15  |  MODULE OF       xscreensaver
16  |
17  |  DESCRIPTION     Smooth and non-linear morphing between 1D curves.
18  |
19  |  WRITTEN BY      Sverre H. Huseby                Glenn T. Lines
20  |                  Kurvn. 30                       Ã˜stgaardsgt. 5
21  |                  N-0495 Oslo                     N-0474 Oslo
22  |                  Norway                          Norway
23  |
24  |                  Phone:  +47 901 63 579          Phone:  +47 22 04 67 28
25  |                  E-mail: sverrehu@online.no      E-mail: glennli@ifi.uio.no
26  |                  URL:    http://home.sol.no/~sverrehu/
27  |
28  |                  The original idea, and the bilinear interpolation
29  |                  mathematics used, emerged in the head of the wise
30  |                  Glenn T. Lines.
31  |
32  |  MODIFICATIONS   october 1999 (shh)
33  |                    * Removed option to use integer arithmetic.
34  |                    * Increased default number of points, and brightened
35  |                      the foreground color a little bit.
36  |                    * Minor code cleanup (very minor, that is).
37  |                    * Default number of steps is no longer random.
38  |                    * Added -linewidth option (and resource).
39  |
40  |                  october 1999 (gtl)
41  |                    * Added cubic interpolation between shapes 
42  |                    * Added non-linear transformation speed  
43  |
44  |                  june 1998 (shh)
45  |                    * Minor code cleanup.
46  |
47  |                  january 1997 (shh)
48  |                    * Some code reformatting.
49  |                    * Added possibility to use float arithmetic.
50  |                    * Added -figtype option.
51  |                    * Made color blue default.
52  |
53  |                  december 1995 (jwz)
54  |                    * Function headers converted from ANSI to K&R.
55  |                    * Added posibility for random number of steps, and
56  |                      made this the default.
57  |
58  |                  march 1995 (shh)
59  |                    * Converted from an MS-Windows program to X Window.
60  |
61  |                  november 1993 (gtl, shh, lots of beer)
62  |                    * Original Windows version (we didn't know better).
63  +----------------------------------------------------------------------*/
64
65 #include <stdio.h>
66 #include <stdlib.h>
67 #include <string.h>
68 #include <math.h>
69 #include "screenhack.h"
70
71 /*-----------------------------------------------------------------------+
72 |  PRIVATE DATA                                                          |
73 +-----------------------------------------------------------------------*/
74
75 /* define MARGINS to make some space around the figure. */
76 #define MARGINS
77
78 #define MAXFIGS    20
79 #define TWO_PI     (2.0 * M_PI)
80 #define RND(x)     (random() % (x))
81
82 #define FT_OPEN    1
83 #define FT_CLOSED  2
84 #define FT_ALL     (FT_OPEN | FT_CLOSED)
85
86 static int
87     numFigs = 0,                /* number of figure arrays. */
88     numPoints,                  /* number of points in each array. */
89     nWork,                      /* current work array number. */
90     nFrom,                      /* current from array number. */
91     nTo,                        /* current to array number. */
92     nNext,                      /* current next array number (after to).*/
93     shift,                      /* shifts the starting point of a figure */
94     figType;
95 static long delay;              /* usecs to wait between updates. */
96 static XPoint
97     *aWork[2],                  /* working arrays. */
98     *a[MAXFIGS],                /* the figure arrays. */
99     *aTmp,                      /* used as source when interrupting morph */
100     *aPrev,                     /* previous points displayed. */
101     *aCurr,                     /* the current points displayed. */  
102     *aFrom,                     /* figure converting from. */
103     *aTo,                       /* figure converting to. */
104     *aNext,                     /* figure converting to next time. */
105     *aSlopeFrom,                /* slope at start of morph */ 
106     *aSlopeTo;                  /* slope at end of morph */ 
107 static int         scrWidth, scrHeight;
108 static double      currGamma, maxGamma = 1.0, deltaGamma;
109 static GC          gcDraw, gcClear;
110 static Display     *dpy;
111 static Window      window;
112
113 /*-----------------------------------------------------------------------+
114 |  PUBLIC DATA                                                           |
115 +-----------------------------------------------------------------------*/
116
117 char *progclass = "LMorph";
118
119 char *defaults [] = {
120     ".background: black",
121     ".foreground: #4444FF",
122     "*points: 200",
123     "*steps: 150",
124     "*delay: 70000",
125     "*figtype: all",
126     "*linewidth: 5",
127     0
128 };
129
130 XrmOptionDescRec options [] = {
131     { "-points",      ".points",      XrmoptionSepArg, 0 },
132     { "-steps",       ".steps",       XrmoptionSepArg, 0 },
133     { "-delay",       ".delay",       XrmoptionSepArg, 0 },
134     { "-figtype",     ".figtype",     XrmoptionSepArg, 0 },
135     { "-linewidth",   ".linewidth",   XrmoptionSepArg, 0 },
136     { 0, 0, 0, 0 }
137 };
138 int options_size = (sizeof (options) / sizeof (options[0]));
139
140 /*-----------------------------------------------------------------------+
141 |  PRIVATE FUNCTIONS                                                     |
142 +-----------------------------------------------------------------------*/
143
144 static void *
145 xmalloc(size_t size)
146 {
147     void *ret;
148
149     if ((ret = malloc(size)) == NULL) {
150         fprintf(stderr, "lmorph: out of memory\n");
151         exit(1);
152     }
153     return ret;
154 }
155
156 static void
157 initPointArrays(void)
158 {
159     int    q, w;
160     int    mx, my;            /* max screen coordinates. */
161     int    mp;                /* max point number. */
162     int    s, rx, ry;
163     int    marginx, marginy;
164     double scalex, scaley;
165
166     mx = scrWidth - 1;
167     my = scrHeight - 1;
168     mp = numPoints - 1;
169
170     aWork[0] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
171     aWork[1] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
172     aTmp     = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
173
174     if (figType & FT_CLOSED) {
175         /* rectangle */
176         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
177         s = numPoints / 4;
178         for (q = 0; q < s; q++) {
179             a[numFigs][q].x = ((double) q / s) * mx;
180             a[numFigs][q].y = 0;
181             a[numFigs][s + q].x = mx;
182             a[numFigs][s + q].y = ((double) q / s) * my;
183             a[numFigs][2 * s + q].x = mx - ((double) q / s) * mx;
184             a[numFigs][2 * s + q].y = my;
185             a[numFigs][3 * s + q].x = 0;
186             a[numFigs][3 * s + q].y = my - ((double) q / s) * my;
187         }
188         for (q = 4 * s; q < numPoints; q++) 
189             a[numFigs][q].x = a[numFigs][q].y = 0;
190         a[numFigs][mp].x = a[numFigs][0].x;
191         a[numFigs][mp].y = a[numFigs][0].y;
192         ++numFigs;
193
194         /*  */
195         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
196         rx = mx / 2;
197         ry = my / 2;
198         for (q = 0; q < numPoints; q++) {
199             a[numFigs][q].x = mx / 2 + rx * sin(1 * TWO_PI * (double) q / mp);
200             a[numFigs][q].y = my / 2 + ry * cos(3 * TWO_PI * (double) q / mp);
201         }
202         a[numFigs][mp].x = a[numFigs][0].x;
203         a[numFigs][mp].y = a[numFigs][0].y;
204         ++numFigs;
205
206         /*  */
207         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
208         rx = mx / 2;
209         ry = my / 2;
210         for (q = 0; q < numPoints; q++) {
211             a[numFigs][q].x = mx / 2 + ry * sin(3 * TWO_PI * (double) q / mp);
212             a[numFigs][q].y = my / 2 + ry * cos(1 * TWO_PI * (double) q / mp);
213         }
214         a[numFigs][mp].x = a[numFigs][0].x;
215         a[numFigs][mp].y = a[numFigs][0].y;
216         ++numFigs;
217
218         /*  */
219         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
220         rx = mx / 2;
221         ry = my / 2;
222         for (q = 0; q < numPoints; q++) {
223             a[numFigs][q].x = mx / 2 + ry 
224                 * (0.8 - 0.2 * sin(30 * TWO_PI * q / mp))
225                 * sin(TWO_PI * (double) q / mp);
226             a[numFigs][q].y = my / 2 + ry
227                 * (0.8 - 0.2 * sin(30 * TWO_PI * q / mp))
228                 * cos(TWO_PI * (double) q / mp);
229         }
230         a[numFigs][mp].x = a[numFigs][0].x;
231         a[numFigs][mp].y = a[numFigs][0].y;
232         ++numFigs;
233
234
235         /* */
236         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
237         rx = mx / 2;
238         ry = my / 2;
239         for (q = 0; q < numPoints; q++) {
240             a[numFigs][q].x = mx / 2 + ry * sin(TWO_PI * (double) q / mp);
241             a[numFigs][q].y = my / 2 + ry * cos(TWO_PI * (double) q / mp);
242         }
243         a[numFigs][mp].x = a[numFigs][0].x;
244         a[numFigs][mp].y = a[numFigs][0].y;
245         ++numFigs;
246
247
248         /*  */
249         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
250         rx = mx / 2;
251         ry = my / 2;
252         for (q = 0; q < numPoints; q++) {
253             a[numFigs][q].x = mx / 2 + rx * cos(TWO_PI * (double) q / mp);
254             a[numFigs][q].y = my / 2 + ry * sin(TWO_PI * (double) q / mp);
255         }
256         a[numFigs][mp].x = a[numFigs][0].x;
257         a[numFigs][mp].y = a[numFigs][0].y;
258         ++numFigs;
259
260         /*  */
261         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
262         rx = mx / 2;
263         ry = my / 2;
264         for (q = 0; q < numPoints; q++) {
265             a[numFigs][q].x = mx / 2 + rx * sin(2 * TWO_PI * (double) q / mp);
266             a[numFigs][q].y = my / 2 + ry * cos(3 * TWO_PI * (double) q / mp);
267         }
268         a[numFigs][mp].x = a[numFigs][0].x;
269         a[numFigs][mp].y = a[numFigs][0].y;
270         ++numFigs;
271     }
272
273     if (figType & FT_OPEN) {
274         /* sine wave, one period */
275         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
276         for (q = 0; q < numPoints; q++) {
277             a[numFigs][q].x = ((double) q / numPoints) * mx;
278             a[numFigs][q].y = (1.0 - sin(((double) q / mp) * TWO_PI))
279                 * my / 2.0;
280         }
281         ++numFigs;
282
283         /*  */
284         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
285         for (q = 0; q < numPoints; q++) {
286             a[numFigs][q].x = ((double) q / mp) * mx;
287             a[numFigs][q].y = (1.0 - cos(((double) q / mp) * 3 * TWO_PI))
288                 * my / 2.0;
289         }
290         ++numFigs;
291
292         /* spiral, one endpoint at bottom */
293         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
294         rx = mx / 2;
295         ry = my / 2;
296         for (q = 0; q < numPoints; q++) {
297             a[numFigs][q].x = mx / 2 + ry * sin(5 * TWO_PI * (double) q / mp)
298                 * ((double) q / mp);
299             a[numFigs][q].y = my / 2 + ry * cos(5 * TWO_PI * (double) q / mp)
300                 * ((double) q / mp);
301         }
302         ++numFigs;
303
304         /* spiral, one endpoint at top */
305         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
306         rx = mx / 2;
307         ry = my / 2;
308         for (q = 0; q < numPoints; q++) {
309             a[numFigs][q].x = mx / 2 + ry * sin(6 * TWO_PI * (double) q / mp)
310                 * ((double) q / mp);
311             a[numFigs][q].y = my / 2 - ry * cos(6 * TWO_PI * (double) q / mp)
312                 * ((double) q / mp);
313         }
314         ++numFigs;
315
316         /*  */
317         a[numFigs] = (XPoint *) xmalloc(numPoints * sizeof(XPoint));
318         for (q = 0; q < numPoints; q++) {
319             a[numFigs][q].x = ((double) q / mp) * mx;
320             a[numFigs][q].y = (1.0 - sin(((double) q / mp) * 5 * TWO_PI))
321                 * my / 2.0;
322         }
323         ++numFigs;
324     }
325
326 #ifdef MARGINS
327     /* make some space around the figures.  */
328     marginx = (mx + 1) / 10;
329     marginy = (my + 1) / 10;
330     scalex = (double) ((mx + 1) - 2.0 * marginx) / (mx + 1.0);
331     scaley = (double) ((my + 1) - 2.0 * marginy) / (my + 1.0);
332     for (q = 0; q < numFigs; q++)
333         for (w = 0; w < numPoints; w++) {
334             a[q][w].x = marginx + a[q][w].x * scalex;
335             a[q][w].y = marginy + a[q][w].y * scaley;
336         }
337 #endif
338 }
339
340 static void
341 initLMorph(void)
342 {
343     int               steps;
344     XGCValues         gcv;
345     XWindowAttributes wa;
346     Colormap          cmap;
347     char              *ft;
348     int               i;
349
350     numPoints = get_integer_resource("points", "Integer");
351     steps = get_integer_resource("steps", "Integer");
352     delay = get_integer_resource("delay", "Integer");
353     ft = get_string_resource("figtype", "String");
354
355     if (strcmp(ft, "all") == 0)
356         figType = FT_ALL;
357     else if (strcmp(ft, "open") == 0)
358         figType = FT_OPEN;
359     else if (strcmp(ft, "closed") == 0)
360         figType = FT_CLOSED;
361     else {
362         fprintf(stderr, "figtype should be `all', `open' or `closed'.\n");
363         figType = FT_ALL;
364     }
365
366     if (steps <= 0)
367       steps = (random() % 400) + 100;
368
369     deltaGamma = 1.0 / steps;
370     XGetWindowAttributes(dpy, window, &wa);
371     scrWidth = wa.width;
372     scrHeight = wa.height;
373     cmap = wa.colormap;
374     gcv.foreground = get_pixel_resource("foreground", "Foreground", dpy, cmap);
375     gcDraw = XCreateGC(dpy, window, GCForeground, &gcv);
376     XSetForeground(dpy, gcDraw, gcv.foreground);
377     gcv.foreground = get_pixel_resource("background", "Background", dpy, cmap);
378     gcClear = XCreateGC(dpy, window, GCForeground, &gcv);
379     XClearWindow(dpy, window);
380
381     srandom(time(NULL));
382     initPointArrays();
383     aCurr = aWork[nWork = 0];
384     aPrev = NULL;
385     currGamma = maxGamma + 1.0;  /* force creation of new figure at startup */
386     nTo = RND(numFigs);
387     do {
388         nNext = RND(numFigs);
389     } while (nNext == nTo);
390
391     aSlopeTo = (XPoint *) xmalloc(numPoints * sizeof(XPoint)); 
392     aSlopeFrom = (XPoint *) xmalloc(numPoints * sizeof(XPoint)); 
393     aNext = (XPoint *) xmalloc(numPoints * sizeof(XPoint)); 
394
395     for (i = 0; i < numPoints ; i++) {
396         aSlopeTo[i].x = 0.0;
397         aSlopeTo[i].y = 0.0; 
398     }
399
400     {   /* jwz for version 2.11 */
401         /*      int width = random() % 10;*/
402         int width = get_integer_resource("linewidth", "Integer");
403         int style = LineSolid;
404         int cap   = (width > 1 ? CapRound  : CapButt);
405         int join  = (width > 1 ? JoinRound : JoinBevel);
406         if (width == 1)
407             width = 0;
408         XSetLineAttributes(dpy, gcDraw,  width, style, cap, join);
409         XSetLineAttributes(dpy, gcClear, width, style, cap, join);
410     }
411 }
412
413 /* 55% of execution time */
414 static void
415 createPoints(void)
416 {
417     int    q;
418     XPoint *pa = aCurr, *pa1 = aFrom, *pa2 = aTo;
419     XPoint *qa1 = aSlopeFrom, *qa2 = aSlopeTo; 
420     float  fg, f1g;
421     float  speed;
422
423     fg  = currGamma;
424     f1g = 1.0 - currGamma;
425     for (q = numPoints; q; q--) {
426         speed = 0.45 * sin(TWO_PI * (double) (q + shift) / (numPoints - 1));
427         fg = currGamma + 1.67 * speed
428             * exp(-200.0 * (currGamma - 0.5 + 0.7 * speed)
429                   * (currGamma - 0.5 + 0.7 * speed));
430
431         f1g = 1.0 - fg;
432         pa->x = (short) (f1g * f1g * f1g * pa1->x + f1g * f1g * fg
433                          * (3 * pa1->x + qa1->x) + f1g * fg * fg
434                          * (3 * pa2->x - qa2->x) + fg * fg * fg * pa2->x);
435         pa->y = (short) (f1g * f1g * f1g * pa1->y + f1g * f1g * fg
436                          * (3 * pa1->y + qa1->y) + f1g * fg * fg
437                          * (3 * pa2->y - qa2->y) + fg * fg * fg * pa2->y);
438
439         ++pa;
440         ++pa1;
441         ++pa2;
442         ++qa1;
443         ++qa2;
444     }
445 }
446
447 /* 36% of execution time */
448 static void
449 drawImage(void)
450 {
451     int    q;
452     XPoint *old0, *old1, *new0, *new1;
453
454     /* Problem: update the window without too much flickering. I do
455      * this by handling each linesegment separately. First remove a
456      * line, then draw the new line. The problem is that this leaves
457      * small black pixels on the figure. To fix this, we draw the
458      * entire figure using XDrawLines() afterwards. */
459     if (aPrev) {
460         old0 = aPrev;
461         old1 = aPrev + 1;
462         new0 = aCurr;
463         new1 = aCurr + 1;
464         for (q = numPoints - 1; q; q--) {
465            XDrawLine(dpy, window, gcClear,
466              old0->x, old0->y, old1->x, old1->y); 
467             XDrawLine(dpy, window, gcDraw,
468                       new0->x, new0->y, new1->x, new1->y);
469             ++old0;
470             ++old1;
471             ++new0;
472             ++new1;
473         }
474     }
475     XDrawLines(dpy, window, gcDraw, aCurr, numPoints, CoordModeOrigin);
476
477     XFlush(dpy);
478 }
479
480 /* neglectible % of execution time */
481 static void
482 animateLMorph(void)
483 {
484     int i;
485     if (currGamma > maxGamma) {
486         currGamma = 0.0;
487         nFrom = nTo;
488         nTo = nNext;
489         aFrom = a[nFrom];
490         aTo = a[nTo];
491         do {
492             nNext = RND(numFigs);
493         } while (nNext == nTo);
494         aNext = a[nNext];
495
496         shift = RND(numPoints);
497         if (RND(2)) {
498             /* reverse the array to get more variation. */
499             int    i1, i2;
500             XPoint p;
501             
502             for (i1 = 0, i2 = numPoints - 1; i1 < numPoints / 2; i1++, i2--) {
503                 p = aNext[i1];
504                 aNext[i1] = aNext[i2];
505                 aNext[i2] = p;
506             }
507         }
508
509         /* calculate the slopes */
510         for (i = 0; i < numPoints ; i++) {
511             aSlopeFrom[i].x = aSlopeTo[i].x;
512             aSlopeFrom[i].y = aSlopeTo[i].y;
513             aSlopeTo[i].x = aNext[i].x - aTo[i].x;
514             aSlopeTo[i].y = (aNext[i].y - aTo[i].y);
515         }
516     }
517
518     createPoints();
519     drawImage();
520     aPrev = aCurr;
521     aCurr = aWork[nWork ^= 1];
522
523     currGamma += deltaGamma;
524 }
525
526 /*-----------------------------------------------------------------------+
527 |  PUBLIC FUNCTIONS                                                      |
528 +-----------------------------------------------------------------------*/
529
530 void
531 screenhack(Display *disp, Window win)
532 {
533     dpy = disp;
534     window = win;
535     initLMorph();
536     for (;;) {
537         animateLMorph();
538         screenhack_handle_events (dpy);
539         usleep(delay);
540         }
541
542 }