http://www.jwz.org/xscreensaver/xscreensaver-5.13.tar.gz
[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 (c) 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 #include "erase.h"
26
27 /* If MAXLINES is too big, we might not be able to get it
28  * to the X server in the 2byte length field. Must be less
29  * than 16k
30  */
31 #define MAXLINES (16 * 1024)
32 #define MAXPOINTS MAXLINES
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
41 struct state {
42   Display *dpy;
43   Window window;
44
45   XPoint *points;
46
47   int sizex, sizey;
48   int delay;
49   int maxlines;
50   GC gc;
51   XColor foreground, background;
52   Colormap cmap;
53
54   eraser_state *eraser;
55   int erase_p;
56 };
57
58
59 /*
60  * Routine (Macro actually)
61  *   mysin
62  * Description:
63  *   Assume that degrees is .. oh 360... meaning that
64  *   there are 360 degress in a circle.  Then this function
65  *   would return the sin of the angle in degrees.  But lets
66  *   say that they are really big degrees, with 4 big degrees
67  *   the same as one regular degree.  Then this routine
68  *   would be called mysin(t, 90) and would return sin(t degrees * 4)
69  */
70 #define mysin(t, degrees) sin(t * 2 * M_PI / (degrees))
71 #define mycos(t, degrees) cos(t * 2 * M_PI / (degrees))
72
73 /*
74  * Macro:
75  *   rand_range
76  * Description:
77  *   Return a random number between a inclusive  and b exclusive.
78  *    rand (3, 6) returns 3 or 4 or 5, but not 6.
79  */
80 #define rand_range(a, b) (a + random() % (b - a))
81
82
83 static int
84 gcd(int m, int n) /* Greatest Common Divisor (also Greates common factor). */
85 {
86     int r;
87
88     for (;;) {
89         r = m % n;
90         if (r == 0) return (n);
91         m = n;
92         n = r;
93     }
94 }
95
96 static int numlines (int a, int b, int d)
97 /*
98  * Description:
99  *
100  *      Given parameters a and b, how many lines will we have to draw?
101  *
102  * Algorithm:
103  *
104  *      This algorithm assumes that r = sin (theta * a), where we
105  *      evaluate theta on multiples of b.
106  *
107  *      LCM (i, j) = i * j / GCD (i, j);
108  *
109  *      So, at LCM (b, 360) we start over again.  But since we
110  *      got to LCM (b, 360) by steps of b, the number of lines is
111  *      LCM (b, 360) / b.
112  *
113  *      If a is odd, then at 180 we cross over and start the
114  *      negative.  Someone should write up an elegant way of proving
115  *      this.  Why?  Because I'm not convinced of it myself. 
116  *
117  */
118 {
119 #define odd(x) (x & 1)
120 #define even(x) (!odd(x))
121     if ( odd(a) && odd(b) && even(d)) d /= 2;
122     return  (d / gcd (d, b));
123 #undef odd
124 }
125
126 static int
127 compute_pedal(struct state *st, XPoint *points, int maxpoints)
128 /*
129  * Description:
130  *
131  *    Basically, it's combination spirograph and string art.
132  *    Instead of doing lines, we just use a complex polygon,
133  *    and use an even/odd rule for filling in between.
134  *
135  *    The spirograph, in mathematical terms is a polar
136  *    plot of the form r = sin (theta * c);
137  *    The string art of this is that we evaluate that
138  *    function only on certain multiples of theta.  That is
139  *    we let theta advance in some random increment.  And then
140  *    we draw a straight line between those two adjacent points.
141  *
142  *    Eventually, the lines will start repeating themselves
143  *    if we've evaluated theta on some rational portion of the
144  *    whole.
145  *
146  *    The number of lines generated is limited to the
147  *    ratio of the increment we put on theta to the whole.
148  *    If we say that there are 360 degrees in a circle, then we
149  *    will never have more than 360 lines.   
150  *
151  * Return:
152  *
153  *    The number of points.
154  *
155  */
156 {
157     int a, b, d;  /* These describe a unique pedal */
158
159     double r;
160     int theta = 0;
161     XPoint *pp = st->points;
162     int count;
163     int numpoints;
164
165     /* Just to make sure that this division is not done inside the loop */
166     int h_width = st->sizex / 2, h_height = st->sizey / 2 ;
167
168     for (;;) {
169         d = rand_range (MINLINES, st->maxlines);
170
171         a = rand_range (1, d);
172         b = rand_range (1, d);
173         numpoints = numlines(a, b, d);
174         if (numpoints > MINLINES) break;
175     }
176
177     /* it might be nice to try to move as much sin and cos computing
178      * (or at least the argument computing) out of the loop.
179      */
180     for (count = numpoints; count-- ; )
181     {
182         r = mysin (theta * a, d);
183
184         /* Convert from polar to cartesian coordinates */
185         /* We could round the results, but coercing seems just fine */
186         pp->x = mysin (theta, d) * r * h_width  + h_width;
187         pp->y = mycos (theta, d) * r * h_height + h_height;
188
189         /* Advance index into array */
190         pp++;
191
192         /* Advance theta */
193         theta += b;
194         theta %= d;
195     }
196
197     return(numpoints);
198 }
199
200 static void *
201 pedal_init (Display *dpy, Window window)
202 {
203   struct state *st = (struct state *) calloc (1, sizeof(*st));
204   XGCValues gcv;
205   XWindowAttributes xgwa;
206
207   st->dpy = dpy;
208   st->window = window;
209
210   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
211   if (st->delay < 0) st->delay = 0;
212
213   st->maxlines = get_integer_resource (st->dpy, "maxlines", "Integer");
214   if (st->maxlines < MINLINES) st->maxlines = MINLINES;
215   else if (st->maxlines > MAXLINES) st->maxlines = MAXLINES;
216
217   st->points = (XPoint *)malloc(sizeof(XPoint) * st->maxlines);
218
219   XGetWindowAttributes (st->dpy, st->window, &xgwa);
220   st->sizex = xgwa.width;
221   st->sizey = xgwa.height;
222
223   st->cmap = xgwa.colormap;
224
225   gcv.function = GXcopy;
226   gcv.foreground = get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
227   gcv.background = get_pixel_resource (st->dpy, st->cmap, "background", "Background");
228   st->gc = XCreateGC (st->dpy, st->window, GCForeground | GCBackground |GCFunction, &gcv);
229
230   return st;
231 }
232
233 /*
234  *    Since the XFillPolygon doesn't require that the last
235  *    point == first point, the number of points is the same
236  *    as the number of lines.  We just let XFillPolygon supply
237  *    the line from the last point to the first point.
238  *
239  */
240 static unsigned long
241 pedal_draw (Display *dpy, Window window, void *closure)
242 {
243   struct state *st = (struct state *) closure;
244   int numpoints;
245   int erase_delay = 10000;
246   int long_delay = 1000000 * st->delay;
247
248   if (st->erase_p || st->eraser) {
249     st->eraser = erase_window (dpy, window, st->eraser);
250     st->erase_p = 0;
251     return (st->eraser ? erase_delay : 1000000);
252   }
253
254   numpoints = compute_pedal(st, st->points, st->maxlines);
255
256   /* Pick a new foreground color (added by jwz) */
257   if (! mono_p)
258     {
259       XColor color;
260       hsv_to_rgb (random()%360, 1.0, 1.0,
261                   &color.red, &color.green, &color.blue);
262       if (XAllocColor (st->dpy, st->cmap, &color))
263         {
264           XSetForeground (st->dpy, st->gc, color.pixel);
265           XFreeColors (st->dpy, st->cmap, &st->foreground.pixel, 1, 0);
266           st->foreground.red = color.red;
267           st->foreground.green = color.green;
268           st->foreground.blue = color.blue;
269           st->foreground.pixel = color.pixel;
270         }
271     }
272
273   XFillPolygon (st->dpy, st->window, st->gc, st->points, numpoints,
274                 Complex, CoordModeOrigin);
275
276   st->erase_p = 1;
277   return long_delay;
278 }
279
280 static void
281 pedal_reshape (Display *dpy, Window window, void *closure, 
282                  unsigned int w, unsigned int h)
283 {
284   struct state *st = (struct state *) closure;
285   st->sizex = w;
286   st->sizey = h;
287 }
288
289 static Bool
290 pedal_event (Display *dpy, Window window, void *closure, XEvent *event)
291 {
292   return False;
293 }
294
295 static void
296 pedal_free (Display *dpy, Window window, void *closure)
297 {
298   struct state *st = (struct state *) closure;
299   free (st);
300 }
301
302
303 \f
304
305 /*
306  * If we are trying to save the screen, the background
307  * should be dark.
308  */
309 static const char *pedal_defaults [] = {
310   ".background:                 black",
311   ".foreground:                 white",
312   "*fpsSolid:                   true",
313   "*delay:                      5",
314   "*maxlines:                   1000",
315   0
316 };
317
318 static XrmOptionDescRec pedal_options [] = {
319   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
320   { "-maxlines",        ".maxlines",            XrmoptionSepArg, 0 },
321   { "-foreground",      ".foreground",          XrmoptionSepArg, 0 },
322   { "-background",      ".background",          XrmoptionSepArg, 0 },
323   { 0, 0, 0, 0 }
324 };
325
326 XSCREENSAVER_MODULE ("Pedal", pedal)