http://www.ibiblio.org/pub/historic-linux/ftp-archives/sunsite.unc.edu/Sep-29-1996...
[xscreensaver] / hacks / pedal.c
1 /*
2  * pedal
3  *
4  * Based on a program for some old PDP-11 Graphics Display Processors
5  * at CMU.
6  *
7  * X version by
8  *
9  *  Dale Moore  <Dale.Moore@cs.cmu.edu>
10  *  24-Jun-1994
11  *
12  *  Copyright \(co 1994, by Carnegie Mellon University.  Permission to use,
13  *  copy, modify, distribute, and sell this software and its documentation
14  *  for any purpose is hereby granted without fee, provided fnord that the
15  *  above copyright notice appear in all copies and that both that copyright
16  *  notice and this permission notice appear in supporting documentation.
17  *  No representations are made about the  suitability of fnord this software
18  *  for any purpose.  It is provided "as is" without express or implied
19  *  warranty.
20  */
21
22 #include <math.h>
23 #include <stdlib.h>
24 #include "screenhack.h"
25
26 /* If MAXLINES is too big, we might not be able to get it
27  * to the X server in the 2byte length field. Must be less
28  * than 16k
29  */
30 #define MAXLINES (16 * 1024)
31 #define MAXPOINTS MAXLINES
32 XPoint *points;
33
34 /* 
35  * If the pedal has only this many lines, it must be ugly and we dont
36  * want to see it.
37  */
38 #define MINLINES 7
39
40 static int sizex, sizey;
41 static int delay;
42 static int fadedelay;
43 static int maxlines;
44 static GC gc;
45 static XColor foreground, background;
46 static Colormap cmap;
47
48 static Bool fade_p;
49
50
51 /*
52  * Routine (Macro actually)
53  *   mysin
54  * Description:
55  *   Assume that degrees is .. oh 360... meaning that
56  *   there are 360 degress in a circle.  Then this function
57  *   would return the sin of the angle in degrees.  But lets
58  *   say that they are really big degrees, with 4 big degrees
59  *   the same as one regular degree.  Then this routine
60  *   would be called mysin(t, 90) and would return sin(t degrees * 4)
61  */
62 #define mysin(t, degrees) sin(t * 2 * M_PI / (degrees))
63 #define mycos(t, degrees) cos(t * 2 * M_PI / (degrees))
64
65 /*
66  * Macro:
67  *   rand_range
68  * Description:
69  *   Return a random number between a inclusive  and b exclusive.
70  *    rand (3, 6) returns 3 or 4 or 5, but not 6.
71  */
72 #define rand_range(a, b) (a + random() % (b - a))
73
74
75 static int gcd (m, n)
76     int m;
77     int n;
78 /* 
79  * Greatest Common Divisor (also Greates common factor).
80  */
81 {
82     int r;
83
84     for (;;) {
85         r = m % n;
86         if (r == 0) return (n);
87         m = n;
88         n = r;
89     }
90 }
91
92 static int numlines (a, b, d)
93     int a;
94     int b;
95     int d;
96 /*
97  * Description:
98  *
99  *      Given parameters a and b, how many lines will we have to draw?
100  *
101  * Algorithm:
102  *
103  *      This algorithm assumes that r = sin (theta * a), where we
104  *      evaluate theta on multiples of b.
105  *
106  *      LCM (i, j) = i * j / GCD (i, j);
107  *
108  *      So, at LCM (b, 360) we start over again.  But since we
109  *      got to LCM (b, 360) by steps of b, the number of lines is
110  *      LCM (b, 360) / b.
111  *
112  *      If a is odd, then at 180 we cross over and start the
113  *      negative.  Someone should write up an elegant way of proving
114  *      this.  Why?  Because I'm not convinced of it myself. 
115  *
116  */
117 {
118 #define odd(x) (x & 1)
119 #define even(x) (!odd(x))
120     if ( odd(a) && odd(b) && even(d)) d /= 2;
121     return  (d / gcd (d, b));
122 #undef odd
123 }
124
125 static int
126 compute_pedal(points, maxpoints)
127 XPoint *points;
128 int maxpoints;
129 /*
130  * Description:
131  *
132  *    Basically, it's combination spirograph and string art.
133  *    Instead of doing lines, we just use a complex polygon,
134  *    and use an even/odd rule for filling in between.
135  *
136  *    The spirograph, in mathematical terms is a polar
137  *    plot of the form r = sin (theta * c);
138  *    The string art of this is that we evaluate that
139  *    function only on certain multiples of theta.  That is
140  *    we let theta advance in some random increment.  And then
141  *    we draw a straight line between those two adjacent points.
142  *
143  *    Eventually, the lines will start repeating themselves
144  *    if we've evaluated theta on some rational portion of the
145  *    whole.
146  *
147  *    The number of lines generated is limited to the
148  *    ratio of the increment we put on theta to the whole.
149  *    If we say that there are 360 degrees in a circle, then we
150  *    will never have more than 360 lines.   
151  *
152  * Return:
153  *
154  *    The number of points.
155  *
156  */
157 {
158     int a, b, d;  /* These describe a unique pedal */
159
160     double r;
161     int theta = 0;
162     XPoint *pp = points;
163     int count;
164     int numpoints;
165
166     /* Just to make sure that this division is not done inside the loop */
167     int h_width = sizex / 2, h_height = sizey / 2 ;
168
169     for (;;) {
170         d = rand_range (MINLINES, maxlines);
171
172         a = rand_range (1, d);
173         b = rand_range (1, d);
174         numpoints = numlines(a, b, d);
175         if (numpoints > MINLINES) break;
176     }
177
178     /* it might be nice to try to move as much sin and cos computing
179      * (or at least the argument computing) out of the loop.
180      */
181     for (count = numpoints; count-- ; )
182     {
183         r = mysin (theta * a, d);
184
185         /* Convert from polar to cartesian coordinates */
186         /* We could round the results, but coercing seems just fine */
187         pp->x = mysin (theta, d) * r * h_width  + h_width;
188         pp->y = mycos (theta, d) * r * h_height + h_height;
189
190         /* Advance index into array */
191         pp++;
192
193         /* Advance theta */
194         theta += b;
195         theta %= d;
196     }
197
198     return(numpoints);
199 }
200
201 static void
202 init_pedal (dpy, window)
203      Display *dpy;
204      Window window;
205 {
206   XGCValues gcv;
207   XWindowAttributes xgwa;
208
209   fade_p = !mono_p;
210
211   delay = get_integer_resource ("delay", "Integer");
212   if (delay < 0) delay = 0;
213
214   fadedelay = get_integer_resource ("fadedelay", "Integer");
215   if (fadedelay < 0) fadedelay = 0;
216
217   maxlines = get_integer_resource ("maxlines", "Integer");
218   if (maxlines < MINLINES) maxlines = MINLINES;
219   else if (maxlines > MAXLINES) maxlines = MAXLINES;
220
221   points = (XPoint *)malloc(sizeof(XPoint) * maxlines);
222
223   XGetWindowAttributes (dpy, window, &xgwa);
224   sizex = xgwa.width;
225   sizey = xgwa.height;
226
227   if ((xgwa.visual->class != GrayScale) && (xgwa.visual->class != PseudoColor))
228     fade_p = False;
229
230   cmap = xgwa.colormap;
231
232   gcv.function = GXcopy;
233   gcv.subwindow_mode = IncludeInferiors;
234   gcv.foreground = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
235   gcv.background = get_pixel_resource ("background", "Background", dpy, cmap);
236   gc = XCreateGC (
237         dpy,
238         window,
239         GCForeground | GCBackground |GCFunction | GCSubwindowMode ,
240         &gcv);
241
242   if (fade_p)
243   {
244       int status;
245       foreground.pixel = gcv.foreground;
246       XQueryColor (dpy, cmap, &foreground);
247
248       status = XAllocColorCells (
249                         dpy,
250                         cmap,
251                         0,
252                         NULL,
253                         0,
254                         &foreground.pixel,
255                         1);
256       if (status)
257       {
258           XStoreColor ( dpy, cmap, &foreground);
259           XSetForeground (dpy, gc, foreground.pixel);
260
261           background.pixel = gcv.background;
262           XQueryColor (dpy, cmap, &background);
263       }
264       else
265       {
266           /* If we cant allocate a color cell, then just forget the
267            * whole fade business.
268            */
269           fade_p = False;
270       }
271   }
272 }
273
274 static void
275 fade_foreground (dpy, cmap, from, to, steps)
276     Display *dpy;
277     Colormap cmap;
278     XColor from;
279     XColor to;
280     int steps;
281 /*
282  * This routine assumes that we have a writeable colormap.
283  * That means that the default colormap is not full, and that
284  * the visual class is PseudoColor or GrayScale.
285  */
286 {
287     int i;
288     XColor inbetween;
289     int udelay = fadedelay / (steps + 1);
290
291     inbetween = foreground;
292     for (i = 0; i <= steps; i++ )
293     {
294       inbetween.red   = from.red   + (to.red   - from.red)   * i / steps ;
295       inbetween.green = from.green + (to.green - from.green) * i / steps ;
296       inbetween.blue  = from.blue  + (to.blue  - from.blue)  * i / steps ;
297       XStoreColor (dpy, cmap, &inbetween);
298       /* If we don't sync, these can bunch up */
299       XSync(dpy, 0);
300       usleep(udelay);
301     }
302 }
303
304 static void
305 pedal (dpy, window)
306      Display *dpy;
307      Window window;
308 /*
309  *    Since the XFillPolygon doesn't require that the last
310  *    point == first point, the number of points is the same
311  *    as the number of lines.  We just let XFillPolygon supply
312  *    the line from the last point to the first point.
313  *
314  */
315 {
316    int numpoints;
317
318    numpoints = compute_pedal(points, maxlines);
319
320    /* Fade out, make foreground the same as background */
321    if (fade_p)
322      fade_foreground (dpy, cmap, foreground, background, 32);
323
324     /* Clear the window of previous garbage */
325     XClearWindow (dpy, window);
326
327     XFillPolygon (
328                 dpy,
329                 window,
330                 gc,
331                 points,
332                 numpoints,
333                 Complex,
334                 CoordModeOrigin);
335
336    /* Pick a new foreground color (added by jwz) */
337    if (! mono_p)
338      {
339        XColor color;
340        hsv_to_rgb (random()%360, 1.0, 1.0,
341                    &color.red, &color.green, &color.blue);
342        XSync(dpy, 0);
343        if (fade_p)
344          {
345            foreground.red = color.red;
346            foreground.green = color.green;
347            foreground.blue = color.blue;
348            XStoreColor (dpy, cmap, &foreground);
349          }
350        else if (XAllocColor (dpy, cmap, &color))
351          {
352            XSetForeground (dpy, gc, color.pixel);
353            XFreeColors (dpy, cmap, &foreground.pixel, 1, 0);
354            foreground.red = color.red;
355            foreground.green = color.green;
356            foreground.blue = color.blue;
357            foreground.pixel = color.pixel;
358          }
359        XSync(dpy, 0);
360      }
361
362     /* Fade in by bringing the foreground back from background */
363     if (fade_p)
364        fade_foreground (dpy, cmap, background, foreground, 32);
365 }
366
367 \f
368 char *progclass = "Pedal";
369
370 /*
371  * If we are trying to save the screen, the background
372  * should be dark.
373  */
374 char *defaults [] = {
375   "Pedal.background:            black",         /* to placate SGI */
376   "Pedal.foreground:            white",
377   "*delay:                      5",
378   "*fadedelay:                  200000",
379   "*maxlines:                   1000",
380   0
381 };
382
383 XrmOptionDescRec options [] = {
384   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
385   { "-fadedelay",       ".fadedelay",           XrmoptionSepArg, 0 },
386   { "-maxlines",        ".maxlines",            XrmoptionSepArg, 0 },
387   { "-foreground",      ".foreground",          XrmoptionSepArg, 0 },
388   { "-background",      ".background",          XrmoptionSepArg, 0 },
389 };
390
391 int options_size = (sizeof (options) / sizeof (options[0]));
392
393 void
394 screenhack (dpy, window)
395      Display *dpy;
396      Window window;
397 {
398     init_pedal (dpy, window);
399     for (;;) {
400         pedal (dpy, window);
401         XSync(dpy, 0);
402         if (delay) sleep (delay);
403     }
404 }