http://svn.poeml.de/viewvc/ppc/src-unpacked/xscreensaver/xscreensaver-4.12.tar.bz2...
[xscreensaver] / hacks / epicycle.c
1 /* epicycle --- The motion of a body with epicycles, as in the pre-Copernican
2  * cosmologies.
3  *
4  * Copyright (c) 1998  James Youngman <jay@gnu.org>
5  * 
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation.  No representations are made about the suitability of this
11  * software for any purpose.  It is provided "as is" without express or 
12  * implied warranty.
13  */
14
15 /* Standard C headers; screenhack.h assumes that these have already
16  * been included if required -- for example, it defines M_PI if not
17  * already defined.
18  */
19 #include <float.h>
20 #include <math.h>
21
22
23 #include "screenhack.h"
24 #include "erase.h"
25
26 /* MIT-SHM headers omitted; this screenhack doesn't use it */
27
28
29
30 /*********************************************************/
31 /******************** MAGIC CONSTANTS ********************/
32 /*********************************************************/
33 #define MIN_RADIUS (5)          /* smallest allowable circle radius */
34 #define FILL_PROPORTION (0.9)   /* proportion of screen to fill by scaling. */
35 /*********************************************************/
36 /***************** END OF MAGIC CONSTANTS ****************/
37 /*********************************************************/
38
39
40
41 #define FULLCIRCLE (2.0 * M_PI) /* radians in a circle. */
42
43
44 /* Name of the Screensaver hack */
45 char *progclass="Epicycle";
46
47 /* Some of these resource values here are hand-tuned to give a
48  * pleasing variety of interesting shapes.  These are not the only
49  * good settings, but you may find you need to change some as a group
50  * to get pleasing figures.
51  */
52 char *defaults [] = {
53   ".background: black",
54   ".foreground: white",
55   "*colors:     100",
56   "*color0:     red",
57   "*delay:      1000",
58   "*holdtime:   2",
59   "*lineWidth:  4",
60   "*minCircles:  2",
61   "*maxCircles:  10",
62   "*minSpeed:   0.003",
63   "*maxSpeed:   0.005",
64   "*harmonics:  8",
65   "*timestep:   1.0",
66   "*timestepCoarseFactor: 1.0", /* no option for this resource. */
67   "*divisorPoisson: 0.4",
68   "*sizeFactorMin: 1.05",
69   "*sizeFactorMax: 2.05",
70   0
71 };
72
73 /* options passed to this program */
74 XrmOptionDescRec options [] = {
75   { "-color0",          ".color0",        XrmoptionSepArg, 0 },
76   { "-colors",          ".colors",        XrmoptionSepArg, 0 },
77   { "-colours",         ".colors",        XrmoptionSepArg, 0 },
78   { "-foreground",      ".foreground",    XrmoptionSepArg, 0 },
79   { "-delay",           ".delay",         XrmoptionSepArg, 0 },
80   { "-holdtime",        ".holdtime",      XrmoptionSepArg, 0 },
81   { "-linewidth",       ".lineWidth",     XrmoptionSepArg, 0 },
82   { "-min_circles",     ".minCircles",    XrmoptionSepArg, 0 },
83   { "-max_circles",     ".maxCircles",    XrmoptionSepArg, 0 },
84   { "-min_speed",       ".minSpeed",      XrmoptionSepArg, 0 },
85   { "-max_speed",       ".maxSpeed",      XrmoptionSepArg, 0 },
86   { "-harmonics",       ".harmonics",     XrmoptionSepArg, 0 },
87   { "-timestep",        ".timestep",      XrmoptionSepArg, 0 },
88   { "-divisor_poisson",".divisorPoisson",XrmoptionSepArg, 0 },
89   { "-size_factor_min", ".sizeFactorMin", XrmoptionSepArg, 0 },
90   { "-size_factor_max", ".sizeFactorMax", XrmoptionSepArg, 0 },
91   { 0, 0, 0, 0 }
92 };
93
94
95 static Display *dpy;
96 static Window window;
97 static GC color0;
98 static int width, height;
99 static int x_offset, y_offset;
100 static int unit_pixels;
101 static unsigned long bg;
102 static Colormap cmap;
103 static int restart = 0;
104 static int stop = 0;
105 static double wdot_max;
106 static XColor *colors = NULL;
107 static int ncolors = 2;
108 static int color_shift_pos=0;   /* how far we are towards that. */
109 static double colour_cycle_rate = 1.0;
110 static int harmonics = 8;
111 static double divisorPoisson = 0.4;
112 static double sizeFactorMin = 1.05;
113 static double sizeFactorMax = 2.05;
114 static int minCircles;
115 static int maxCircles;
116
117 /* Each circle is centred on a point on the rim of another circle.
118  */
119 struct tagCircle
120 {
121   long radius;                  /* in pixels */
122   double w;                     /* position (radians ccw from x-axis) */
123   double initial_w;             /* starting position */
124   double wdot;                  /* rotation rate (change in w per iteration) */
125   int    divisor;
126
127   struct tagCircle *pchild;
128 };
129 typedef struct tagCircle Circle;
130
131
132 struct tagBody                  /* a body that moves on a system of circles. */
133 {
134   int x_origin, y_origin;
135   int x, y;
136   int old_x, old_y;
137   int current_color;            /* pixel index into colors[] */
138   Circle *epicycles;            /* system of circles on which it moves. */
139   struct tagBody *next;         /* next in list. */
140 };
141 typedef struct tagBody Body;
142
143
144 /* Determine the GCD of two numbers using Euclid's method.  The other
145  * possible algorighm is Stein's method, but it's probably only going
146  * to be much faster on machines with no divide instruction, like the
147  * ARM and the Z80.  The former is very fast anyway and the latter
148  * probably won't run X clients; in any case, this calculation is not
149  * the bulk of the computational expense of the program.  I originally
150  * tried using Stein's method, but I wanted to remove the gotos.  Not
151  * wanting to introduce possible bugs, I plumped for Euclid's method
152  * instead.  Lastly, Euclid's algorithm is preferred to the
153  * generalisation for N inputs.
154  *
155  * See Knuth, section 4.5.2.
156  */
157 static int
158 gcd(int u, int v)               /* Euclid's Method */
159 {
160   /* If either operand of % is negative, the sign of the result is
161    * implementation-defined.  See section 6.3.5 "Multiplicative
162    * Operators" of the ANSI C Standard (page 46 [LEFT HAND PAGE!] of
163    * "Annotated C Standard", Osborne, ISBN 0-07-881952-0).
164    */
165   if (u < 0) u = -u;
166   if (v < 0) v = -v;
167   
168   while (0 != v)
169     {
170       int r;
171       r = u % v;
172       u = v;
173       v = r;
174     }
175   return u;
176 }
177
178 /* Determine the Lowest Common Multiple of two integers, using
179  * Euclid's Proposition 34, as explained in Knuth's The Art of
180  * Computer Programming, Vol 2, section 4.5.2.
181  */
182 static int
183 lcm(int u, int v)
184 {
185   return u / gcd(u,v) * v;
186 }
187
188 static long 
189 random_radius(double scale)     
190 {
191   long r;
192
193   r = frand(scale) * unit_pixels/2; /* for frand() see utils/yarandom.h */
194   if (r < MIN_RADIUS)
195     r = MIN_RADIUS;
196   return r;
197 }
198
199
200 static long
201 random_divisor(void)
202 {
203   int divisor = 1;
204   int sign;
205
206   while (frand(1.0) < divisorPoisson && divisor <= harmonics)
207     {
208       ++divisor;
209     }
210   sign = (frand(1.0) < 0.5) ? +1 : -1;
211   return sign * divisor;
212 }
213
214
215 static void
216 oom(void)
217 {
218   fprintf(stderr, "Failed to allocate memory!\n");
219   exit(-1);
220 }
221
222
223 /* Construct a circle or die.
224  */
225 Circle *
226 new_circle(double scale)
227 {
228   Circle *p = malloc(sizeof(Circle));
229   
230   p->radius = random_radius(scale);
231   p->w = p->initial_w = 0.0;
232   p->divisor = random_divisor();
233   p->wdot = wdot_max / p->divisor;
234   p->pchild = NULL;
235   
236   return p;
237 }
238
239 static void delete_circle(Circle *p)
240 {
241   free(p);
242 }
243
244 static void 
245 delete_circle_chain(Circle *p)
246 {
247   while (p)
248     {
249       Circle *q = p->pchild;
250       delete_circle(p);
251       p = q;
252     }
253 }
254
255 Circle *
256 new_circle_chain(void)
257 {
258   Circle *head;
259   double scale = 1.0, factor;
260   int n;
261
262   /* Parent circles are larger than their children by a factor of at
263    * least FACTOR_MIN and at most FACTOR_MAX.
264    */
265   factor = sizeFactorMin + frand(sizeFactorMax - sizeFactorMin);
266   
267   /* There are between minCircles and maxCircles in each figure.
268    */
269   if (maxCircles == minCircles)
270     n = minCircles;            /* Avoid division by zero. */
271   else
272     n = minCircles + random() % (maxCircles - minCircles);
273   
274   head = NULL;
275   while (n--)
276     {
277       Circle *p = new_circle(scale);
278       p->pchild = head;
279       head = p;
280
281       scale /= factor;
282     }
283   return head;
284 }
285
286 static void
287 assign_random_common_w(Circle *p)
288 {
289   double w_common = frand(FULLCIRCLE);  /* anywhere on the circle */
290   while (p)
291     {
292       p->initial_w = w_common;
293       p = p->pchild;
294     }
295 }
296
297 static Body *
298 new_body(void)
299 {
300   Body *p = malloc(sizeof(Body));
301   if (NULL == p)
302     oom();
303   p->epicycles = new_circle_chain();
304   p->current_color = 0;         /* ?? start them all on different colors? */
305   p->next = NULL;
306   p->x = p->y = 0;
307   p->old_x = p->old_y = 0;
308   p->x_origin = p->y_origin = 0;
309
310   /* Start all the epicycles at the same w value to make it easier to
311    * figure out at what T value the cycle is closed.   We don't just fix
312    * the initial W value because that makes all the patterns tend to 
313    * be symmetrical about the X axis.
314    */
315   assign_random_common_w(p->epicycles);
316   return p;
317 }
318
319 static void
320 delete_body(Body *p)
321 {
322   delete_circle_chain(p->epicycles);
323   free(p);
324 }
325
326
327 static void 
328 draw_body(Body *pb, GC gc)
329 {
330   XDrawLine(dpy, window, gc, pb->old_x, pb->old_y, pb->x, pb->y);
331 }
332
333 static long
334 compute_divisor_lcm(Circle *p)
335 {
336   long l = 1;
337   
338   while (p)
339     {
340       l = lcm(l, p->divisor);
341       p = p->pchild;
342     }
343   return l;
344 }
345
346               
347 /* move_body()
348  *
349  * Calculate the position for the body at time T.  We work in double 
350  * rather than int to avoid the cumulative errors that would be caused
351  * by the rounding implicit in an assignment to int.
352  */
353 static void
354 move_body(Body *pb, double t)
355 {
356   Circle *p;
357   double x, y;
358
359   pb->old_x = pb->x;
360   pb->old_y = pb->y;
361   
362   x = pb->x_origin;
363   y = pb->y_origin;
364   
365   for (p=pb->epicycles; NULL != p; p=p->pchild)
366     {
367       /* angular pos = initial_pos + time * angular speed */
368       /* but this is an angular position, so modulo FULLCIRCLE. */
369       p->w = fmod(p->initial_w + (t * p->wdot), FULLCIRCLE);
370       
371       x += (p->radius * cos(p->w));
372       y += (p->radius * sin(p->w));
373     }
374   
375   pb->x = (int)x;
376   pb->y = (int)y;
377 }
378
379 static int
380 colour_init(XWindowAttributes *pxgwa)
381 {
382   XGCValues gcv;
383
384 #if 0
385   int H = random() % 360;       /* colour choice from attraction.c. */
386   double S1 = 0.25;
387   double S2 = 1.00;
388   double V = frand(0.25) + 0.75;
389   int line_width = 0;
390 #endif
391
392   int retval = 1;
393   unsigned long valuemask = 0L;
394   unsigned long fg;
395   
396   /* Free any already allocated colors...
397    */
398   if (colors)
399     {
400       free_colors(dpy, cmap, colors, ncolors);
401       colors = 0;
402       ncolors = 0;
403     }
404         
405   ncolors = get_integer_resource ("colors", "Colors");
406   if (0 == ncolors)             /* English spelling? */
407     ncolors = get_integer_resource ("colours", "Colors");
408   
409   if (ncolors < 2)
410     ncolors = 2;
411   if (ncolors <= 2)
412     mono_p = True;
413   colors = 0;
414
415   if (!mono_p)
416     {
417       colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
418       if (!colors)
419         oom();
420           
421       make_smooth_colormap (dpy, pxgwa->visual, cmap, colors, &ncolors,
422                             True, /* allocate */
423                             False, /* not writable */
424                             True); /* verbose (complain about failure) */
425       if (ncolors <= 2)
426         {
427           if (colors)
428             free (colors);
429           colors = 0;
430           mono_p = True;
431         }
432     }
433
434   
435   bg = get_pixel_resource ("background", "Background", dpy, cmap);
436
437   /* Set the line width
438    */
439   gcv.line_width = get_integer_resource ("lineWidth", "Integer");
440   if (gcv.line_width)
441     {
442       valuemask |= GCLineWidth;
443
444       gcv.join_style = JoinRound;
445       gcv.cap_style = CapRound;
446           
447       valuemask |= (GCCapStyle | GCJoinStyle);
448     }
449   
450
451   /* Set the drawing function.
452    */
453   gcv.function = GXcopy;
454   valuemask |= GCFunction;
455   
456   /* Set the foreground.
457    */
458   if (mono_p)
459     fg = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
460   else
461     fg = bg ^ get_pixel_resource (("color0"), "Foreground", dpy, cmap); 
462   gcv.foreground = fg;
463   valuemask |= GCForeground;
464
465   /* Actually create the GC.
466    */
467   color0 = XCreateGC (dpy, window, valuemask, &gcv);
468   
469   return retval;
470 }
471
472
473 /* check_events(); originally from XScreensaver: hacks/maze.c,
474  * but now quite heavily modified.
475  *
476  * Reaction to events:-
477  *
478  * Mouse 1 -- new figure }
479  *       2 -- new figure }-- ignored when running on root window.
480  *       3 -- exit       }
481  *
482  * Window resized or exposed -- new figure.
483  * Window iconised -- wait until it's re-mapped, then start a new figure.
484  */
485 static int
486 check_events (void)                        /* X event handler [ rhess ] */
487 {
488   XEvent e;
489   int unmapped = 0;
490         
491   while (unmapped || XPending(dpy))
492     {
493       XNextEvent(dpy, &e);
494                 
495       switch (e.type)
496         {
497         case ButtonPress:
498           switch (e.xbutton.button)
499             {
500             case 3:
501               exit (0);
502               break;
503                                 
504             case 2:
505             case 1:
506             default:
507               restart = 1 ;
508               stop = 0 ;
509               break;
510             }
511           break;
512                         
513         case ConfigureNotify:
514           restart = 1;
515           break;
516                         
517         case UnmapNotify:
518           printf("unmapped!\n");
519           unmapped = 1;
520           restart = 1;                  /* restart with new fig. when re-mapped. */
521           break;
522                         
523         case Expose:            
524           if (0 == e.xexpose.count)
525             {
526                                 /* We can get several expose events in the queue.
527                                  * Only the last one has a zero count.  We eat
528                                  * events in this function so as to avoid restarting
529                                  * the screensaver many times in quick succession.
530                                  */
531               restart = 1;
532             }
533           /* If we had been unmapped and are now waiting to be re-mapped,
534            * indicate that we condition we are waiting for is now met.
535            */
536           if (unmapped)
537             printf("re-mapped!\n");
538           unmapped = 0;
539           break;
540
541         default:
542           screenhack_handle_event(dpy, &e);
543           break;
544         }
545                 
546       /* If we're unmapped, don't return to the caller.  This
547        * prevents us wasting CPU, calculating new positions for
548        * things that will never be plotted.   This is a real CPU
549        * saver.
550        */
551       if (!unmapped)
552         return 1;
553     }
554   return 0;
555 }
556
557
558 static void
559 setup(void)
560 {
561   XWindowAttributes xgwa;
562   int root;
563   
564   XGetWindowAttributes (dpy, window, &xgwa);
565   cmap = xgwa.colormap;
566
567   width = xgwa.width;
568   height = xgwa.height;
569   x_offset = width / 2;
570   y_offset = height / 2;
571   unit_pixels = width < height ? width : height;
572
573   {
574     static Bool done = False;
575     if (!done)
576       {
577         colour_init(&xgwa);
578         done = True;
579       }
580   }
581   
582   root = get_boolean_resource("root", "Boolean");
583   
584   if (root)
585     {
586       XSelectInput(dpy, window, ExposureMask);
587     }
588   else
589     {
590       XGetWindowAttributes (dpy, window, &xgwa);
591       XSelectInput (dpy, window,
592                     xgwa.your_event_mask | ExposureMask |
593                     ButtonPressMask |StructureNotifyMask);
594     }
595   
596 }
597 static void
598 color_step(Body *pb, double frac)
599 {
600   if (!mono_p)
601     {
602       int newshift = ncolors * fmod(frac * colour_cycle_rate, 1.0);
603       if (newshift != color_shift_pos)
604         {
605           pb->current_color = newshift;
606           XSetForeground (dpy, color0, colors[pb->current_color].pixel);
607           color_shift_pos = newshift;
608         }
609     }
610 }
611
612
613 long
614 distance(long x1, long y1, long x2, long y2)
615 {
616   long dx, dy;
617
618   dx = x2 - x1;
619   dy = y2 - y1;
620   return dx*dx + dy*dy;
621 }
622
623 #if 0
624 static int poisson_irand(double p)
625 {
626   int r = 1;
627   while (fabs(frand(1.0)) < p)
628     ++r;
629   return r < 1 ? 1 : r;
630 }
631 #endif
632
633 static void
634 precalculate_figure(Body *pb,
635                     double xtime, double step,
636                     int *x_max, int *y_max,
637                     int *x_min, int *y_min)
638 {
639   double t;
640   
641   move_body(pb, 0.0); /* move once to avoid initial line from origin */
642   *x_min = *x_max = pb->x;
643   *y_min = *y_max = pb->y;
644   
645   for (t=0.0; t<xtime; t += step)
646     {
647       move_body(pb, t); /* move once to avoid initial line from origin */
648       if (pb->x > *x_max)
649         *x_max = pb->x;
650       if (pb->x < *x_min)
651         *x_min = pb->x;
652       if (pb->y > *y_max)
653         *y_max = pb->y;
654       if (pb->y < *y_min)
655         *y_min = pb->y;
656     }
657 }
658
659 static int i_max(int a, int b)
660 {
661   return (a>b) ? a : b;
662 }
663
664 static void rescale_circles(Body *pb,
665                             int x_max, int y_max,
666                             int x_min, int y_min)
667 {
668   double xscale, yscale, scale;
669   double xm, ym;
670   
671   x_max -= x_offset;
672   x_min -= x_offset;
673   y_max -= y_offset;
674   y_min -= y_offset;
675
676   x_max = i_max(x_max, -x_min);
677   y_max = i_max(y_max, -y_min);
678
679
680   xm = width / 2.0;
681   ym = height / 2.0;
682   if (x_max > xm)
683     xscale = xm / x_max;
684   else
685     xscale = 1.0;
686   if (y_max > ym)
687     yscale = ym / y_max;
688   else
689     yscale = 1.0;
690
691   if (xscale < yscale)          /* wider than tall */
692     scale = xscale;             /* ensure width fits onscreen */
693   else
694     scale = yscale;             /* ensure height fits onscreen */
695
696
697   scale *= FILL_PROPORTION;     /* only fill FILL_PROPORTION of screen */
698   if (scale < 1.0)              /* only reduce, don't enlarge. */
699     {
700       Circle *p;
701       for (p=pb->epicycles; p; p=p->pchild)
702         {
703           p->radius *= scale;
704         }
705     }
706   else
707     {
708       printf("enlarge by x%.2f skipped...\n", scale);
709     }
710 }
711
712
713 /* angular speeds of the circles are harmonics of a fundamental
714  * value.  That should please the Pythagoreans among you... :-)
715  */
716 static double 
717 random_wdot_max(void)
718 {
719   /* Maximum and minimum values for the choice of wdot_max.  Possible
720    * epicycle speeds vary from wdot_max to (wdot_max * harmonics).
721    */
722   double minspeed, maxspeed;
723   minspeed = get_float_resource("minSpeed", "Double");
724   maxspeed = get_float_resource("maxSpeed", "Double");
725   return harmonics * (minspeed + FULLCIRCLE * frand(maxspeed-minspeed));
726 }
727
728 /* this is the function called for your screensaver */
729 /*GLOBAL*/ void
730 screenhack(Display *disp, Window win)
731 {
732   Body *pb = NULL;
733   long l;
734   double t, timestep, circle, xtime, timestep_coarse;
735   int delay;
736   int uncleared = 1;
737   int xmax, xmin, ymax, ymin;
738   int holdtime = get_integer_resource ("holdtime", "Integer");
739
740   dpy = disp;
741   window = win;
742
743   circle = FULLCIRCLE;
744   
745   XClearWindow(dpy, window);
746   uncleared = 0;
747   
748   delay = get_integer_resource ("delay", "Integer");
749   harmonics = get_integer_resource("harmonics", "Integer");
750   divisorPoisson = get_float_resource("divisorPoisson", "Double");
751   
752   timestep = get_float_resource("timestep", "Double");
753   timestep_coarse = timestep *
754     get_float_resource("timestepCoarseFactor", "Double");
755   
756   sizeFactorMin = get_float_resource("sizeFactorMin", "Double");
757   sizeFactorMax = get_float_resource("sizeFactorMax", "Double");
758
759   minCircles = get_integer_resource ("minCircles", "Integer");
760   maxCircles = get_integer_resource ("maxCircles", "Integer");
761
762   xtime = 0; /* is this right? */
763   while (0 == stop)
764     {
765       setup(); /* do this inside the loop to cope with any window resizing */
766       restart = 0;
767
768       /* Flush any outstanding events; this has the side effect of
769        * reducing the number of "false restarts"; resdtarts caused by
770        * one event (e.g. ConfigureNotify) followed by another
771        * (e.g. Expose).
772        */
773       XSync(dpy, True);
774           
775       wdot_max = random_wdot_max();
776           
777       if (pb)
778         {
779           delete_body(pb);
780           pb = NULL;
781         }
782       pb = new_body();
783       pb->x_origin = pb->x = x_offset;
784       pb->y_origin = pb->y = y_offset;
785           
786       
787       if (uncleared)
788         {
789           erase_full_window(dpy, window);
790           uncleared = 0;
791         }
792
793       fflush(stdout);
794       precalculate_figure(pb, xtime, timestep_coarse,
795                           &xmax, &ymax, &xmin, &ymin);
796
797       rescale_circles(pb, xmax, ymax, xmin, ymin);
798       
799       move_body(pb, 0.0); /* move once to avoid initial line from origin */
800       move_body(pb, 0.0); /* move once to avoid initial line from origin */
801
802       
803       t = 0.0;                  /* start at time zero. */
804
805       l = compute_divisor_lcm(pb->epicycles);
806       
807       colour_cycle_rate = fabs(l);
808       
809       xtime = fabs(l * circle / wdot_max);
810
811       if (colors)                               /* (colors==NULL) if mono_p */
812         XSetForeground (dpy, color0, colors[pb->current_color].pixel);
813
814       while (0 == restart)
815         {
816           color_step(pb, t/xtime );
817           draw_body(pb, color0);
818           uncleared = 1;
819
820           
821           /* Check if the figure is complete...*/
822           if (t > xtime)
823             {
824               XSync (dpy, False);
825
826               check_events();
827               if (holdtime)
828                 sleep(holdtime); /* show complete figure for a bit. */
829
830               restart = 1;      /* begin new figure. */
831             }
832           
833           
834           check_events();
835           if (delay)
836             usleep (delay);
837           
838           t += timestep;
839           move_body(pb, t);
840           check_events();
841         }
842     }
843 }
844