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 /* Some of these resource values here are hand-tuned to give a
45 * pleasing variety of interesting shapes. These are not the only
46 * good settings, but you may find you need to change some as a group
47 * to get pleasing figures.
49 static const char *epicycle_defaults [] = {
64 "*timestepCoarseFactor: 1.0", /* no option for this resource. */
65 "*divisorPoisson: 0.4",
66 "*sizeFactorMin: 1.05",
67 "*sizeFactorMax: 2.05",
69 "*ignoreRotation: True",
74 /* options passed to this program */
75 static XrmOptionDescRec epicycle_options [] = {
76 { "-color0", ".color0", XrmoptionSepArg, 0 },
77 { "-colors", ".colors", XrmoptionSepArg, 0 },
78 { "-colours", ".colors", XrmoptionSepArg, 0 },
79 { "-foreground", ".foreground", XrmoptionSepArg, 0 },
80 { "-delay", ".delay", XrmoptionSepArg, 0 },
81 { "-holdtime", ".holdtime", XrmoptionSepArg, 0 },
82 { "-linewidth", ".lineWidth", XrmoptionSepArg, 0 },
83 { "-min_circles", ".minCircles", XrmoptionSepArg, 0 },
84 { "-max_circles", ".maxCircles", XrmoptionSepArg, 0 },
85 { "-min_speed", ".minSpeed", XrmoptionSepArg, 0 },
86 { "-max_speed", ".maxSpeed", XrmoptionSepArg, 0 },
87 { "-harmonics", ".harmonics", XrmoptionSepArg, 0 },
88 { "-timestep", ".timestep", XrmoptionSepArg, 0 },
89 { "-divisor_poisson",".divisorPoisson",XrmoptionSepArg, 0 },
90 { "-size_factor_min", ".sizeFactorMin", XrmoptionSepArg, 0 },
91 { "-size_factor_max", ".sizeFactorMax", XrmoptionSepArg, 0 },
96 /* Each circle is centred on a point on the rim of another circle.
100 long radius; /* in pixels */
101 double w; /* position (radians ccw from x-axis) */
102 double initial_w; /* starting position */
103 double wdot; /* rotation rate (change in w per iteration) */
106 struct tagCircle *pchild;
108 typedef struct tagCircle Circle;
111 struct tagBody /* a body that moves on a system of circles. */
113 int x_origin, y_origin;
116 int current_color; /* pixel index into colors[] */
117 Circle *epicycles; /* system of circles on which it moves. */
118 struct tagBody *next; /* next in list. */
120 typedef struct tagBody Body;
128 int x_offset, y_offset;
136 int color_shift_pos; /* how far we are towards that. */
137 double colour_cycle_rate;
139 double divisorPoisson;
140 double sizeFactorMin;
141 double sizeFactorMax;
148 double T, timestep, circle, timestep_coarse;
152 int xmax, xmin, ymax, ymin;
155 eraser_state *eraser;
160 /* Determine the GCD of two numbers using Euclid's method. The other
161 * possible algorighm is Stein's method, but it's probably only going
162 * to be much faster on machines with no divide instruction, like the
163 * ARM and the Z80. The former is very fast anyway and the latter
164 * probably won't run X clients; in any case, this calculation is not
165 * the bulk of the computational expense of the program. I originally
166 * tried using Stein's method, but I wanted to remove the gotos. Not
167 * wanting to introduce possible bugs, I plumped for Euclid's method
168 * instead. Lastly, Euclid's algorithm is preferred to the
169 * generalisation for N inputs.
171 * See Knuth, section 4.5.2.
174 gcd(int u, int v) /* Euclid's Method */
176 /* If either operand of % is negative, the sign of the result is
177 * implementation-defined. See section 6.3.5 "Multiplicative
178 * Operators" of the ANSI C Standard (page 46 [LEFT HAND PAGE!] of
179 * "Annotated C Standard", Osborne, ISBN 0-07-881952-0).
194 /* Determine the Lowest Common Multiple of two integers, using
195 * Euclid's Proposition 34, as explained in Knuth's The Art of
196 * Computer Programming, Vol 2, section 4.5.2.
201 return u / gcd(u,v) * v;
205 random_radius(struct state *st, double scale)
209 r = frand(scale) * st->unit_pixels/2; /* for frand() see utils/yarandom.h */
217 random_divisor(struct state *st)
222 while (frand(1.0) < st->divisorPoisson && divisor <= st->harmonics)
226 sign = (frand(1.0) < 0.5) ? +1 : -1;
227 return sign * divisor;
231 /* Construct a circle or die.
234 new_circle(struct state *st, double scale)
236 Circle *p = malloc(sizeof(Circle));
238 p->radius = random_radius(st, scale);
239 p->w = p->initial_w = 0.0;
240 p->divisor = random_divisor(st);
241 p->wdot = st->wdot_max / p->divisor;
247 static void delete_circle(Circle *p)
253 delete_circle_chain(Circle *p)
257 Circle *q = p->pchild;
264 new_circle_chain(struct state *st)
267 double scale = 1.0, factor;
270 /* Parent circles are larger than their children by a factor of at
271 * least FACTOR_MIN and at most FACTOR_MAX.
273 factor = st->sizeFactorMin + frand(st->sizeFactorMax - st->sizeFactorMin);
275 /* There are between minCircles and maxCircles in each figure.
277 if (st->maxCircles == st->minCircles)
278 n = st->minCircles; /* Avoid division by zero. */
280 n = st->minCircles + random() % (st->maxCircles - st->minCircles);
285 Circle *p = new_circle(st, scale);
295 assign_random_common_w(Circle *p)
297 double w_common = frand(FULLCIRCLE); /* anywhere on the circle */
300 p->initial_w = w_common;
306 new_body(struct state *st)
308 Body *p = malloc(sizeof(Body));
310 p->epicycles = new_circle_chain(st);
311 p->current_color = 0; /* ?? start them all on different colors? */
314 p->old_x = p->old_y = 0;
315 p->x_origin = p->y_origin = 0;
317 /* Start all the epicycles at the same w value to make it easier to
318 * figure out at what T value the cycle is closed. We don't just fix
319 * the initial W value because that makes all the patterns tend to
320 * be symmetrical about the X axis.
322 assign_random_common_w(p->epicycles);
329 delete_circle_chain(p->epicycles);
335 draw_body(struct state *st, Body *pb, GC gc)
337 XDrawLine(st->dpy, st->window, gc, pb->old_x, pb->old_y, pb->x, pb->y);
341 compute_divisor_lcm(Circle *p)
347 l = lcm(l, p->divisor);
356 * Calculate the position for the body at time T. We work in double
357 * rather than int to avoid the cumulative errors that would be caused
358 * by the rounding implicit in an assignment to int.
361 move_body(Body *pb, double t)
372 for (p=pb->epicycles; NULL != p; p=p->pchild)
374 /* angular pos = initial_pos + time * angular speed */
375 /* but this is an angular position, so modulo FULLCIRCLE. */
376 p->w = fmod(p->initial_w + (t * p->wdot), FULLCIRCLE);
378 x += (p->radius * cos(p->w));
379 y += (p->radius * sin(p->w));
387 colour_init(struct state *st, XWindowAttributes *pxgwa)
392 int H = random() % 360; /* colour choice from attraction.c. */
395 double V = frand(0.25) + 0.75;
400 unsigned long valuemask = 0L;
403 /* Free any already allocated colors...
407 free_colors(pxgwa->screen, st->cmap, st->colors, st->ncolors);
412 st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");
413 if (0 == st->ncolors) /* English spelling? */
414 st->ncolors = get_integer_resource (st->dpy, "colours", "Colors");
418 if (st->ncolors <= 2)
424 st->colors = (XColor *) malloc(sizeof(*st->colors) * (st->ncolors+1));
425 if (!st->colors) abort();
427 make_smooth_colormap (pxgwa->screen, pxgwa->visual, st->cmap,
428 st->colors, &st->ncolors,
430 False, /* not writable */
431 True); /* verbose (complain about failure) */
432 if (st->ncolors <= 2)
442 st->bg = get_pixel_resource (st->dpy, st->cmap, "background", "Background");
444 /* Set the line width
446 gcv.line_width = get_integer_resource (st->dpy, "lineWidth", "Integer");
449 valuemask |= GCLineWidth;
451 gcv.join_style = JoinRound;
452 gcv.cap_style = CapRound;
454 valuemask |= (GCCapStyle | GCJoinStyle);
458 /* Set the drawing function.
460 gcv.function = GXcopy;
461 valuemask |= GCFunction;
463 /* Set the foreground.
466 fg = get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
469 fg = st->bg ^ get_pixel_resource (st->dpy, st->cmap, ("color0"), "Foreground");
472 valuemask |= GCForeground;
474 /* Actually create the GC.
476 st->color0 = XCreateGC (st->dpy, st->window, valuemask, &gcv);
485 setup(struct state *st)
487 XWindowAttributes xgwa;
489 XGetWindowAttributes (st->dpy, st->window, &xgwa);
490 st->cmap = xgwa.colormap;
492 st->width = xgwa.width;
493 st->height = xgwa.height;
494 st->x_offset = st->width / 2;
495 st->y_offset = st->height / 2;
496 st->unit_pixels = st->width < st->height ? st->width : st->height;
501 colour_init(st, &xgwa);
509 color_step(struct state *st, Body *pb, double frac)
513 int newshift = st->ncolors * fmod(frac * st->colour_cycle_rate, 1.0);
514 if (newshift != st->color_shift_pos)
516 pb->current_color = newshift;
517 XSetForeground (st->dpy, st->color0, st->colors[pb->current_color].pixel);
518 st->color_shift_pos = newshift;
526 distance(long x1, long y1, long x2, long y2)
532 return dx*dx + dy*dy;
535 static int poisson_irand(double p)
538 while (fabs(frand(1.0)) < p)
540 return r < 1 ? 1 : r;
545 precalculate_figure(Body *pb,
546 double this_xtime, double step,
547 int *x_max, int *y_max,
548 int *x_min, int *y_min)
552 move_body(pb, 0.0); /* move once to avoid initial line from origin */
553 *x_min = *x_max = pb->x;
554 *y_min = *y_max = pb->y;
556 for (t=0.0; t<this_xtime; t += step)
558 move_body(pb, t); /* move once to avoid initial line from origin */
570 static int i_max(int a, int b)
572 return (a>b) ? a : b;
575 static void rescale_circles(struct state *st, Body *pb,
576 int x_max, int y_max,
577 int x_min, int y_min)
579 double xscale, yscale, scale;
582 x_max -= st->x_offset;
583 x_min -= st->x_offset;
584 y_max -= st->y_offset;
585 y_min -= st->y_offset;
587 x_max = i_max(x_max, -x_min);
588 y_max = i_max(y_max, -y_min);
591 xm = st->width / 2.0;
592 ym = st->height / 2.0;
602 if (xscale < yscale) /* wider than tall */
603 scale = xscale; /* ensure width fits onscreen */
605 scale = yscale; /* ensure height fits onscreen */
608 scale *= FILL_PROPORTION; /* only fill FILL_PROPORTION of screen */
609 if (scale < 1.0) /* only reduce, don't enlarge. */
612 for (p=pb->epicycles; p; p=p->pchild)
619 printf("enlarge by x%.2f skipped...\n", scale);
624 /* angular speeds of the circles are harmonics of a fundamental
625 * value. That should please the Pythagoreans among you... :-)
628 random_wdot_max(struct state *st)
630 /* Maximum and minimum values for the choice of wdot_max. Possible
631 * epicycle speeds vary from wdot_max to (wdot_max * harmonics).
633 double minspeed, maxspeed;
634 minspeed = get_float_resource(st->dpy, "minSpeed", "Double");
635 maxspeed = get_float_resource(st->dpy, "maxSpeed", "Double");
636 return st->harmonics * (minspeed + FULLCIRCLE * frand(maxspeed-minspeed));
641 epicycle_init (Display *disp, Window win)
643 struct state *st = (struct state *) calloc (1, sizeof(*st));
647 st->holdtime = get_integer_resource (st->dpy, "holdtime", "Integer");
649 st->circle = FULLCIRCLE;
651 XClearWindow(st->dpy, st->window);
655 st->delay = get_integer_resource (st->dpy, "delay", "Integer");
656 st->harmonics = get_integer_resource(st->dpy, "harmonics", "Integer");
657 st->divisorPoisson = get_float_resource(st->dpy, "divisorPoisson", "Double");
659 st->timestep = get_float_resource(st->dpy, "timestep", "Double");
660 st->timestep_coarse = st->timestep *
661 get_float_resource(st->dpy, "timestepCoarseFactor", "Double");
663 st->sizeFactorMin = get_float_resource(st->dpy, "sizeFactorMin", "Double");
664 st->sizeFactorMax = get_float_resource(st->dpy, "sizeFactorMax", "Double");
666 st->minCircles = get_integer_resource (st->dpy, "minCircles", "Integer");
667 st->maxCircles = get_integer_resource (st->dpy, "maxCircles", "Integer");
669 st->xtime = 0; /* is this right? */
675 epicycle_draw (Display *dpy, Window window, void *closure)
677 struct state *st = (struct state *) closure;
678 int this_delay = st->delay;
681 st->eraser = erase_window (st->dpy, st->window, st->eraser);
690 /* Flush any outstanding events; this has the side effect of
691 * reducing the number of "false restarts"; resdtarts caused by
692 * one event (e.g. ConfigureNotify) followed by another
696 st->wdot_max = random_wdot_max(st);
700 delete_body(st->pb0);
703 st->pb0 = new_body(st);
704 st->pb0->x_origin = st->pb0->x = st->x_offset;
705 st->pb0->y_origin = st->pb0->y = st->y_offset;
709 st->eraser = erase_window (st->dpy, st->window, st->eraser);
713 precalculate_figure(st->pb0, st->xtime, st->timestep_coarse,
714 &st->xmax, &st->ymax, &st->xmin, &st->ymin);
716 rescale_circles(st, st->pb0, st->xmax, st->ymax, st->xmin, st->ymin);
718 move_body(st->pb0, 0.0); /* move once to avoid initial line from origin */
719 move_body(st->pb0, 0.0); /* move once to avoid initial line from origin */
722 st->T = 0.0; /* start at time zero. */
724 st->L = compute_divisor_lcm(st->pb0->epicycles);
726 st->colour_cycle_rate = fabs(st->L);
728 st->xtime = fabs(st->L * st->circle / st->wdot_max);
730 if (st->colors) /* (colors==NULL) if mono_p */
731 XSetForeground (st->dpy, st->color0, st->colors[st->pb0->current_color].pixel);
735 color_step(st, st->pb0, st->T/st->xtime );
736 draw_body(st, st->pb0, st->color0);
740 /* Check if the figure is complete...*/
741 if (st->T > st->xtime)
743 this_delay = st->holdtime * 1000000;
744 st->restart = 1; /* begin new figure. */
749 st->T += st->timestep;
750 move_body(st->pb0, st->T);
756 epicycle_reshape (Display *dpy, Window window, void *closure,
757 unsigned int w, unsigned int h)
759 struct state *st = (struct state *) closure;
764 epicycle_event (Display *dpy, Window window, void *closure, XEvent *e)
766 struct state *st = (struct state *) closure;
767 if (e->type == ButtonPress)
777 epicycle_free (Display *dpy, Window window, void *closure)
779 struct state *st = (struct state *) closure;
783 XSCREENSAVER_MODULE ("Epicycle", epicycle)