[comp.lang.postscript] Spirograph

CXT105@psuvm.psu.edu (Christopher Tate) (10/26/90)

Quite a few people have asked me for my code to draw Spirograph(tm) pictures,
so here it is.  I'm afraid I have to clear up a mistaken impression I gave
in my original post:  the Spirograph(tm) code only draws black-and-white,
not color.  However, it will soon, so when that's ready, I'll post the
pertinent changes (or the original, depending on how I feel :-).

For anyone not familiar with Spirograph (gasp!), the concept is that the
picture is drawn by putting a pen through a hole in a small toothed disk,
at some (variable) distance from the center of the disk, and then rolling
the disk around the inside of a toothed ring whose inner radius is greater
than the radius of the disk.  You with me so far?

In mathematical talk, this translates to a curve called a "cycloid,"
which is a class of curves defined by rolling a disk around something,
and watching what a particular point on the disk does.  The curves are
usually expressed parametrically; the equations for true Spirograph
pictures are as follows:

               x = (R - r) * cos(theta) + a * cos(phi)
               y = (R - r) * sin(theta) + a * sin(phi)

where R, r, and a are the ring diameter, the disk diameter, and the
distance from the center of the disk to the pen, respectively.  Theta
is the main parameter of angle around the circle; it starts at zero and
increases.  The picture starts repeating when

                         2 * pi * r
               theta =  ------------
                          gcd(R, r)

so this is used as the limit on theta when actually drawing the picture.
Phi is a function of theta, it is the rotational position of the disk at
any given point, and is calculated by

                      (R - r)
               phi = --------- * theta
                         r

(Sorry for all the math, but this really is a complex class of curves,
and it took me some time to figure out the relation between phi and
theta.  Of course, this probably shows that Math is the wrong major for
me, but oh, well....  Now you can impress your enemies and frighten
your friends by knowing the parametric equations for Spirograph!)

This process generates finite curves because the ratio of the radii of
the ring and the disk is a rational number.  In the real Spirograph, there
are several different disks to choose from, with holes at various places
inside them, and a couple of rings of different diameters.  In my program,
the user specifies the radius of the disk (called littleR in the code), the
radius of the ring (called bigR), and the distance that the "pen" is placed
from the center of the disk.  Unlike the "analog" version of Spirograph,
in my version you can have the pen sitting somewhere off the disk....  :)

So, here's the code.  I'll post a color version as soon as I have one --
the idea I have right now is to gradually change the pen color as the
picture develops.  Should be pretty impressive!

(As an added bonus, the program contains a well-behaved procedure for
calculating the Greatest Common Divisor of two integers.  The procedure
creates and disposes of its own temporary dictionary, to avoid trashing
any existing variables, and runs very quickly.)

-------
Christopher Tate                           | Mercy (noun):
                                           |  The infrequent art of turning
  Bitnet: cxt105@psuvm                     |  thumbs-up on your opponent at
  Uucp:   ...!psuvax1!psuvm.bitnet!cxt105  |  the end of your rapier.

------------------------------- Cut Here ----------------------------
%!PS-Adobe-1.0

%   This program generates a Spirograph(tm) image centered on the page
%   according to the three parameters bigR, littleR, and a.  Images are
%   generated by calculating successive positions of the cycloid generated
%   by rolling a small disk inside a larger ring, with a "pen" placed some
%   distance from the center of the disk.
%
%   bigR is the radius of the "outer" circle (the ring, in Spirograph(tm))
%   littleR is the radius of the "inner" circle (the disk)
%   a is the distance of the "pen" from the center of the inner circle.
%
%   Since the program calculates the total angle through which to
%   parameterize (i.e. how long it has to roll the disk around in the
%   ring) by looking for a greatest common divisor, the values of the
%   parameters bigR, littleR, and a MUST be integers.
%
%   If you're interested, the parametric equations which describe a
%   Spirograph(tm) image are given by:
%
%         x = (R - r) * cos(theta) + a * cos(phi)
%         y = (R - r) * sin(theta) + a * sin(phi),
%
%   where phi = ((R - r) / r) * theta.
%
%   This program was written by Christopher Tate, and is hereby placed
%   into the public domain.  Anyone may freely borrow from or modify
%   this program without permission.  Have fun!

/bigR 200 def         % just change these to define a new image
/littleR 81 def       % littleR should be less than bigR
/a 120 def            % a need not be smaller than littleR

% Euclid's Greatest Common Divisor algorithm to find gcd(m, n)
% Calling process:  m n *gcd* result

/gcd
{
   3 dict begin      % isolate these variables from the rest of the program
   /m exch def /n exch def
   /r m n mod def
   {
      r 0 eq {exit} if      % quit when r equals zero
      /m n def
      /n r def
      /r m n mod def
   } loop
   n                        % put result on the stack
   end                      % restore the original dictionary context
} def

clippath pathbbox      % find the bounding box of the page

/ury exch def          % remember Upper Right y
/urx exch def          % remember URx
/lly exch def          % remember LLy
/llx exch def          % remember LLx

urx llx sub 2 div llx add
ury lly sub 2 div lly add
translate              % move the origin to the center of the page

/diff bigR littleR sub def              % diff = R - r
/frac 1.0 bigR littleR div sub def      % frac = 1.0 - (R/r)

.08 setlinewidth           % sufficiently thin lines
newpath
diff a add 0 moveto        % set initial position

/limit 360 littleR mul bigR littleR gcd div def    % how far we need to go

0 3 limit                  % this is the main loop
{
   dup frac mul /phi exch def       % calculate phi
   /theta exch def                  % recalculate theta
   theta cos diff mul phi cos a mul add   % this is x
   theta sin diff mul phi sin a mul add   % this is y
   2 copy lineto stroke moveto      % same as x y lineto stroke x y moveto
} for

showpage