1 /* epicycle --- The motion of a body with epicycles, as in the pre-Copernican
4 * Copyright (c) 1998 James Youngman <jay@gnu.org>
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
15 /* Standard C headers; screenhack.h assumes that these have already
16 * been included if required -- for example, it defines M_PI if not
23 #include "screenhack.h"
26 /* MIT-SHM headers omitted; this screenhack doesn't use it */
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 /*********************************************************/
41 #define FULLCIRCLE (2.0 * M_PI) /* radians in a circle. */
44 /* Name of the Screensaver hack */
45 char *progclass="Epicycle";
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.
66 "*timestepCoarseFactor: 1.0", /* no option for this resource. */
67 "*divisorPoisson: 0.4",
68 "*sizeFactorMin: 1.05",
69 "*sizeFactorMax: 2.05",
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 },
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;
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;
117 /* Each circle is centred on a point on the rim of another circle.
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) */
127 struct tagCircle *pchild;
129 typedef struct tagCircle Circle;
132 struct tagBody /* a body that moves on a system of circles. */
134 int x_origin, y_origin;
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. */
141 typedef struct tagBody Body;
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.
155 * See Knuth, section 4.5.2.
158 gcd(int u, int v) /* Euclid's Method */
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).
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.
185 return u / gcd(u,v) * v;
189 random_radius(double scale)
193 r = frand(scale) * unit_pixels/2; /* for frand() see utils/yarandom.h */
206 while (frand(1.0) < divisorPoisson && divisor <= harmonics)
210 sign = (frand(1.0) < 0.5) ? +1 : -1;
211 return sign * divisor;
218 fprintf(stderr, "Failed to allocate memory!\n");
223 /* Construct a circle or die.
226 new_circle(double scale)
228 Circle *p = malloc(sizeof(Circle));
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;
239 static void delete_circle(Circle *p)
245 delete_circle_chain(Circle *p)
249 Circle *q = p->pchild;
256 new_circle_chain(void)
259 double scale = 1.0, factor;
262 /* Parent circles are larger than their children by a factor of at
263 * least FACTOR_MIN and at most FACTOR_MAX.
265 factor = sizeFactorMin + frand(sizeFactorMax - sizeFactorMin);
267 /* There are between minCircles and maxCircles in each figure.
269 if (maxCircles == minCircles)
270 n = minCircles; /* Avoid division by zero. */
272 n = minCircles + random() % (maxCircles - minCircles);
277 Circle *p = new_circle(scale);
287 assign_random_common_w(Circle *p)
289 double w_common = frand(FULLCIRCLE); /* anywhere on the circle */
292 p->initial_w = w_common;
300 Body *p = malloc(sizeof(Body));
303 p->epicycles = new_circle_chain();
304 p->current_color = 0; /* ?? start them all on different colors? */
307 p->old_x = p->old_y = 0;
308 p->x_origin = p->y_origin = 0;
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.
315 assign_random_common_w(p->epicycles);
322 delete_circle_chain(p->epicycles);
328 draw_body(Body *pb, GC gc)
330 XDrawLine(dpy, window, gc, pb->old_x, pb->old_y, pb->x, pb->y);
334 compute_divisor_lcm(Circle *p)
340 l = lcm(l, p->divisor);
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.
354 move_body(Body *pb, double t)
365 for (p=pb->epicycles; NULL != p; p=p->pchild)
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);
371 x += (p->radius * cos(p->w));
372 y += (p->radius * sin(p->w));
380 colour_init(XWindowAttributes *pxgwa)
385 int H = random() % 360; /* colour choice from attraction.c. */
388 double V = frand(0.25) + 0.75;
393 unsigned long valuemask = 0L;
396 /* Free any already allocated colors...
400 free_colors(dpy, cmap, colors, ncolors);
405 ncolors = get_integer_resource ("colors", "Colors");
406 if (0 == ncolors) /* English spelling? */
407 ncolors = get_integer_resource ("colours", "Colors");
417 colors = (XColor *) malloc(sizeof(*colors) * (ncolors+1));
421 make_smooth_colormap (dpy, pxgwa->visual, cmap, colors, &ncolors,
423 False, /* not writable */
424 True); /* verbose (complain about failure) */
435 bg = get_pixel_resource ("background", "Background", dpy, cmap);
437 /* Set the line width
439 gcv.line_width = get_integer_resource ("lineWidth", "Integer");
442 valuemask |= GCLineWidth;
444 gcv.join_style = JoinRound;
445 gcv.cap_style = CapRound;
447 valuemask |= (GCCapStyle | GCJoinStyle);
451 /* Set the drawing function.
453 gcv.function = GXcopy;
454 valuemask |= GCFunction;
456 /* Set the foreground.
459 fg = get_pixel_resource ("foreground", "Foreground", dpy, cmap);
461 fg = bg ^ get_pixel_resource (("color0"), "Foreground", dpy, cmap);
463 valuemask |= GCForeground;
465 /* Actually create the GC.
467 color0 = XCreateGC (dpy, window, valuemask, &gcv);
473 /* check_events(); originally from XScreensaver: hacks/maze.c,
474 * but now quite heavily modified.
476 * Reaction to events:-
478 * Mouse 1 -- new figure }
479 * 2 -- new figure }-- ignored when running on root window.
482 * Window resized or exposed -- new figure.
483 * Window iconised -- wait until it's re-mapped, then start a new figure.
486 check_events (void) /* X event handler [ rhess ] */
491 while (unmapped || XPending(dpy))
498 switch (e.xbutton.button)
513 case ConfigureNotify:
518 printf("unmapped!\n");
520 restart = 1; /* restart with new fig. when re-mapped. */
524 if (0 == e.xexpose.count)
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.
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.
537 printf("re-mapped!\n");
542 screenhack_handle_event(dpy, &e);
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
561 XWindowAttributes xgwa;
564 XGetWindowAttributes (dpy, window, &xgwa);
565 cmap = xgwa.colormap;
568 height = xgwa.height;
569 x_offset = width / 2;
570 y_offset = height / 2;
571 unit_pixels = width < height ? width : height;
574 static Bool done = False;
582 root = get_boolean_resource("root", "Boolean");
586 XSelectInput(dpy, window, ExposureMask);
590 XWindowAttributes xgwa;
591 XGetWindowAttributes (dpy, window, &xgwa);
592 XSelectInput (dpy, window,
593 xgwa.your_event_mask | ExposureMask |
594 ButtonPressMask |StructureNotifyMask);
599 color_step(Body *pb, double frac)
603 int newshift = ncolors * fmod(frac * colour_cycle_rate, 1.0);
604 if (newshift != color_shift_pos)
606 pb->current_color = newshift;
607 XSetForeground (dpy, color0, colors[pb->current_color].pixel);
608 color_shift_pos = newshift;
615 distance(long x1, long y1, long x2, long y2)
621 return dx*dx + dy*dy;
625 static int poisson_irand(double p)
628 while (fabs(frand(1.0)) < p)
630 return r < 1 ? 1 : r;
635 precalculate_figure(Body *pb,
636 double xtime, double step,
637 int *x_max, int *y_max,
638 int *x_min, int *y_min)
642 move_body(pb, 0.0); /* move once to avoid initial line from origin */
643 *x_min = *x_max = pb->x;
644 *y_min = *y_max = pb->y;
646 for (t=0.0; t<xtime; t += step)
648 move_body(pb, t); /* move once to avoid initial line from origin */
660 static int i_max(int a, int b)
662 return (a>b) ? a : b;
665 static void rescale_circles(Body *pb,
666 int x_max, int y_max,
667 int x_min, int y_min)
669 double xscale, yscale, scale;
677 x_max = i_max(x_max, -x_min);
678 y_max = i_max(y_max, -y_min);
692 if (xscale < yscale) /* wider than tall */
693 scale = xscale; /* ensure width fits onscreen */
695 scale = yscale; /* ensure height fits onscreen */
698 scale *= FILL_PROPORTION; /* only fill FILL_PROPORTION of screen */
699 if (scale < 1.0) /* only reduce, don't enlarge. */
702 for (p=pb->epicycles; p; p=p->pchild)
709 printf("enlarge by x%.2f skipped...\n", scale);
714 /* angular speeds of the circles are harmonics of a fundamental
715 * value. That should please the Pythagoreans among you... :-)
718 random_wdot_max(void)
720 /* Maximum and minimum values for the choice of wdot_max. Possible
721 * epicycle speeds vary from wdot_max to (wdot_max * harmonics).
723 double minspeed, maxspeed;
724 minspeed = get_float_resource("minSpeed", "Double");
725 maxspeed = get_float_resource("maxSpeed", "Double");
726 return harmonics * (minspeed + FULLCIRCLE * frand(maxspeed-minspeed));
729 /* this is the function called for your screensaver */
731 screenhack(Display *disp, Window win)
735 double t, timestep, circle, xtime, timestep_coarse;
738 int xmax, xmin, ymax, ymin;
739 int holdtime = get_integer_resource ("holdtime", "Integer");
746 XClearWindow(dpy, window);
749 delay = get_integer_resource ("delay", "Integer");
750 harmonics = get_integer_resource("harmonics", "Integer");
751 divisorPoisson = get_float_resource("divisorPoisson", "Double");
753 timestep = get_float_resource("timestep", "Double");
754 timestep_coarse = timestep *
755 get_float_resource("timestepCoarseFactor", "Double");
757 sizeFactorMin = get_float_resource("sizeFactorMin", "Double");
758 sizeFactorMax = get_float_resource("sizeFactorMax", "Double");
760 minCircles = get_integer_resource ("minCircles", "Integer");
761 maxCircles = get_integer_resource ("maxCircles", "Integer");
763 xtime = 0; /* is this right? */
766 setup(); /* do this inside the loop to cope with any window resizing */
769 /* Flush any outstanding events; this has the side effect of
770 * reducing the number of "false restarts"; resdtarts caused by
771 * one event (e.g. ConfigureNotify) followed by another
776 wdot_max = random_wdot_max();
784 pb->x_origin = pb->x = x_offset;
785 pb->y_origin = pb->y = y_offset;
790 erase_full_window(dpy, window);
795 precalculate_figure(pb, xtime, timestep_coarse,
796 &xmax, &ymax, &xmin, &ymin);
798 rescale_circles(pb, xmax, ymax, xmin, ymin);
800 move_body(pb, 0.0); /* move once to avoid initial line from origin */
801 move_body(pb, 0.0); /* move once to avoid initial line from origin */
804 t = 0.0; /* start at time zero. */
806 l = compute_divisor_lcm(pb->epicycles);
808 colour_cycle_rate = fabs(l);
810 xtime = fabs(l * circle / wdot_max);
812 if (colors) /* (colors==NULL) if mono_p */
813 XSetForeground (dpy, color0, colors[pb->current_color].pixel);
817 color_step(pb, t/xtime );
818 draw_body(pb, color0);
822 /* Check if the figure is complete...*/
829 sleep(holdtime); /* show complete figure for a bit. */
831 restart = 1; /* begin new figure. */