http://www.jwz.org/xscreensaver/xscreensaver-5.09.tar.gz
[xscreensaver] / hacks / glx / juggler3d.c
index cdf6187a502c4db9c68de40b4cf9457d19a136e8..5697ed263af32c44e98aed77dde5e2e6320627c5 100644 (file)
-/* Juggler3D, Copyright (c) 2005-2008 Brian Apps <brian@jugglesaver.co.uk>
+/* juggle, Copyright (c) 1996-2009 Tim Auckland <tda10.geo@yahoo.com>
+ * and Jamie Zawinski <jwz@jwz.org>
  *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * the above copyright notice appear in all copies and that both that copyright
- * notice and this permission notice appear in supporting documentation.  No
- * representations are made about the suitability of this software for any
- * purpose.  It is provided "as is" without express or implied warranty. */
-
-#undef countof
-#define countof(x) (sizeof((x))/sizeof((*x)))
-
-#define DEFAULTS    \
-    "*delay: 20000\n*showFPS: False\n*wireframe: False\n"
-
-# define refresh_juggler3d 0
-# define release_juggler3d 0
-#include "xlockmore.h"
-#include "gltrackball.h"
-
-#ifdef USE_GL /* whole file */
-
-/* A selection of macros to make functions from math.h return single precision
- * numbers.  Arguably it's better to work at a higher precision and cast it
- * back but littering the code with casts makes it less readable -- without
- * the casts you can get tons of warnings from the compiler (particularily
- * MSVC which enables loss of precision warnings by default) */
-#define cosf(a) (float)(cos((a)))
-#define sinf(a) (float)(sin((a)))
-#define tanf(a) (float)(tan((a)))
-#define sqrtf(a) (float)(sqrt((a)))
-#define powf(a, b) (float)(pow((a), (b)))
-
-#undef max
-#undef min
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation for any purpose and without fee is hereby granted,
+ * provided that the above copyright notice appear in all copies and that
+ * both that copyright notice and this permission notice appear in
+ * supporting documentation.
+ *
+ * This file is provided AS IS with no warranties of any kind.  The author
+ * shall have no liability with respect to the infringement of copyrights,
+ * trade secrets or any patents by this file or any part thereof.  In no
+ * event will the author be liable for any lost revenue or profits or
+ * other special, indirect and consequential damages.
+ *
+ * NOTE: this program was originally called "juggle" and was 2D Xlib.
+ *       There was another program called "juggler3d" that was OpenGL.
+ *       In 2009, jwz converted "juggle" to OpenGL and renamed
+ *       "juggle" to "juggler3d".  The old "juggler3d" hack is gone.
+ *
+ * Revision History
+ * 09-Aug-2009: jwz: converted from Xlib to OpenGL.
+ * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
+ *              Add -rings, -bballs.  Add -describe.  Finally made
+ *              live pattern updates possible.  Add refill_juggle(),
+ *              change_juggle() and reshape_juggle().  Make
+ *              init_juggle() non-destructive.  Reorder erase/draw
+ *              operations.  Update xscreensaver xml and manpage.
+ * 15-Nov-2004: [TDA] Fix all memory leaks.
+ * 12-Nov-2004: [TDA] Add -torches and another new trail
+ *              implementation, so that different objects can have
+ *              different length trails.
+ * 11-Nov-2004: [TDA] Clap when all the balls are in the air.
+ * 10-Nov-2004: [TDA] Display pattern name converted to hight
+ *              notation.
+ * 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
+ * 02-Sep-2003: Non-real time to see what is happening without a
+ *              strobe effect for slow machines.
+ * 01-Nov-2000: Allocation checks
+ * 1996: Written
+ */
 
-#define max(a, b) ((a) > (b) ? (a) : (b))
-#define min(a, b) ((a) < (b) ? (a) : (b))
+/*-
+ * TODO
+ * Implement the anonymously promised -uni option.
+ */
 
 
-/******************************************************************************
+/*
+ * Notes on Adam Chalcraft Juggling Notation (used by permission)
+ * a-> Adam's notation  s-> Site swap (Cambridge) notation
  *
- * The code is broadly split into the following parts:
+ * To define a map from a-notation to s-notation ("site-swap"), both
+ * of which look like doubly infinite sequences of natural numbers. In
+ * s-notation, there is a restriction on what is allowed, namely for
+ * the sequence s_n, the associated function f(n)=n+s_n must be a
+ * bijection. In a-notation, there is no restriction.
  *
- *  - Engine.  The process of determining the position of the juggler and 
- *        objects being juggled at an arbitrary point in time.  This is
- *        independent from any drawing code.
- *  - Sites.  The process of creating a random site swap pattern or parsing
- *        a Juggle Saver compatible siteswap for use by the engine.  For an
- *        introduction to juggling site swaps check out
- *         http://www.jugglingdb.com/
- *  - Rendering.  OpenGL drawing code that animates the juggler.
- *  - XScreenSaver.  Interface code to get thing working as a GLX hack.
- *  
- *****************************************************************************/
-
-
-/*****************************************************************************
+ * To go from a-notation to s-notation, you start by mapping each a_n
+ * to a permutation of N, the natural numbers.
  *
- * Data structures
+ * 0 -> the identity
+ * 1 -> (10) [i.e. f(1)=0, f(0)=1]
+ * 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
+ * 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
+ * etc.
  *
- *****************************************************************************/
-
-/* POS is used to represent the position of a hand when it catches or throws
- * an object; as well as the orientation of the object.  The rotation and
- * elevation are specified in degrees.  These angles are not normalised so that
- * it is possible to specify how the object spins and rotates as it is thrown
- * from the 'From' position to the 'To' position.
- * 
- * Because this is the position of the hand some translation is required with
- * rings and clubs to get the centre of rotation position. */
-
-typedef struct
-{
-    float x;
-    float y;
-    float z;
-    float Rot;
-    float Elev;
-} POS;
-
-
-/* An array of THROW_INFOs are configured with each entry corresponding to the
- * position in the site swap (In fact we double up odd length patterns to ensure
- * there is left/right symmetry).  It allows us to quickly determine where an
- * object and the hands are at a given time.  The information is specified in
- * terms of throws, and positions where throws aren't present (0's and 2's) are
- * simply ignored.
- * 
- * TotalTime - The count of beats before this object is thrown again.  Typically
- *    this is the same as the weight of the throw but where an object is held it
- *    is longer.  e.g. the first throw of the site 64242.7. will be 10, 6 for
- *    throw and 4 (two 2's) for the carry.
- * TimeInAir - The weight of the throw.
- * PrevThrow - zero based index into array of THROW_INFOs of the previous throw.
- *     e.g. for the throw '8' in the site 345678..... the PrevThrow is 1
- *    (i.e. the 4)
- * FromPos, FromVelocity, ToPos, ToVelocity - The position and speeds at the
- *    start and end of the throw.  These are used to generate a spline while
- *    carrying an object and while moving the hand from a throw to a catch.
- * NextForHand - Number of beats before the hand that throws this object will
- *    throw another object.  This is always going to be at least 2.  When there
- *    are gaps in the pattern (0's) or holds (2's) NextForHand increases. */
-
-typedef struct
-{
-    int TotalTime;
-    int TimeInAir;
-    int PrevThrow;
-
-    POS FromPos;
-    POS FromVelocity;
-    POS ToPos;
-    POS ToVelocity;
-
-    int NextForHand;
-} THROW_INFO;
-
-
-/* OBJECT_POSITION works with the array of THROW_INFOs to allow us to determine
- * exactly where an object or hand is.
+ * Then for each n, you look at how long 0 takes to get back to 0
+ * again and you call this t_n. If a_n=0, for example, then since the
+ * identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
+ * a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
+ * next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
+ * following the 1. Finally, set s_n = t_n - 1.
  *
- * TimeOffset - The total number of beats expired when the object was thrown.
- * ThrowIndex - The zero based index into the THROW_INFO array for the current
- *     throw.
- * ObjectType - One of the OBJECT_XX defines.
- * TotalTwist - Only relevant for OBJECT_BALL, this is the total amount the ball
- *     has twisted while in the air.  When segmented balls are drawn you see a 
- *     spinning effect similar to what happens when you juggle beanbags.  */
-
-#define OBJECT_DEFAULT 0
-#define OBJECT_BALL 1
-#define OBJECT_CLUB 2
-#define OBJECT_RING 3
-
-typedef struct
-{
-    int TimeOffset;
-    int ThrowIndex;
-    float TotalTwist;
-    int ObjectType;
-} OBJECT_POSITION;
-
-
-/* PATTERN_INFO is the main structure that holds the information about a 
- * juggling pattern. 
+ * To give some examples, it helps to have a notation for cyclic
+ * sequences. By (123), for example, I mean ...123123123123... . Now
+ * under the a-notation -> s-notation mapping we have some familiar
+ * examples:
  *
- * pThrowInfo is an array of ThrowLen elements that describes each throw in the
- *     pattern.
- * pObjectInfo gives the current position of all objects at a given instant.
- *     These values are updated as the pattern is animated.
- * LeftHand and RightHand describe the current positions of each of the 
- *     juggler's hands.
- * MaxWeight is the maximum weight of the all throws in pThrowInfo.
- * Height and Alpha are parameters that describe how objects fall under the
- *     influence of gravity.  See SetHeightAndAlpha() for the gory details. */
-
-typedef struct
-{
-    THROW_INFO* pThrowInfo;
-    int ThrowLen;
-    
-    OBJECT_POSITION* pObjectInfo;
-    int Objects;
-
-    OBJECT_POSITION LeftHand;
-    OBJECT_POSITION RightHand;
-    
-    int MaxWeight;
-
-    float Height;
-    float Alpha;
-} PATTERN_INFO;
-
-
-/* EXT_SITE_INFO is used to initialise a PATTERN_INFO object using a Juggle
- * Saver compatible site swap.  These contain additional information about the
- * type of object thrown, the positions of throw and catch etc. */
-
-#define HAS_FROM_POS 1
-#define HAS_TO_POS 2
-#define HAS_SNATCH 4
-#define HAS_SPINS 8
-
-typedef struct
-{
-    unsigned Flags;
-    int Weight;
-    int ObjectType;
-    POS FromPos;
-    POS ToPos;
-    float SnatchX;
-    float SnatchY;
-    int Spins;
-} EXT_SITE_INFO;
-
-
-/* RENDER_STATE is used to co-ordinate the OpenGL rendering of the juggler and
- * objects:
- * pPattern - The pattern to be juggled
- * CameraElev - The elevation angle (in degrees) that the camera is looking
- *    along.  0 is horizontal and a +ve angle is looking down.  This value
- *    should be between -90 and +90.
- * AspectRatio - Window width to height ratio.
- * DLStart - The number for the first display list created, any others directly
- *    follow this.
- * Time - Animation time (in beats)
- * TranslateAngle - Cumulative translation (in degrees) for the juggling figure.
- * SpinAngle- Cumulative spin (in degrees) for the juggling figure.
- */
-
-typedef struct
-{
-    PATTERN_INFO* pPattern;
-    float CameraElev;
-    float AspectRatio;
-    int DLStart;
-    
-    float Time;
-    float TranslateAngle;
-    float SpinAngle;
-    
-    trackball_state *trackball;
-    Bool button_down_p;
-
-} RENDER_STATE;
-
-
-/*****************************************************************************
+ * (0)->(0), (1)->(1), (2)->(2) etc.
+ * (21)->(31), (31)->(51), (41)->(71) etc.
+ * (10)->(20), (20)->(40), (30)->(60) etc.
+ * (331)->(441), (312)->(612), (303)->(504), (321)->(531)
+ * (43)->(53), (434)->(534), (433)->(633)
+ * (552)->(672)
+ *
+ * In general, the number of balls is the *average* of the s-notation,
+ * and the *maximum* of the a-notation. Another theorem is that the
+ * minimum values in the a-notation and the s-notation and equal, and
+ * preserved in the same positions.
+ *
+ * The usefulness of a-notation is the fact that there are no
+ * restrictions on what is allowed. This makes random juggle
+ * generation much easier. It also makes enumeration very
+ * easy. Another handy feature is computing changes.  Suppose you can
+ * do (5) and want a neat change up to (771) in s-notation [Mike Day
+ * actually needed this example!]. Write them both in a-notation,
+ * which gives (5) and (551). Now concatenate them (in general, there
+ * may be more than one way to do this, but not in this example), to
+ * get
+ *
+ * ...55555555551551551551551...
+ *
+ * Now convert back to s-notation, to get
  *
- * Engine
+ * ...55555566771771771771771...
  *
- ****************************************************************************
+ * So the answer is to do two 6 throws and then go straight into
+ * (771).  Coming back down of course,
  *
- * The main purpose of the engine is to work out the exact position of all the
- * juggling objects and the juggler's hands at any point in time.  The motion
- * of the objects can be split into two parts: in the air and and being carried.
+ * ...5515515515515515555555555...
  *
- * While in the air, the motion is governed by a standard parabolic trajectory.
- * The only minor complication is that the engine has no fixed concept of
- * gravity, instead it using a term called Alpha that varies according to the
- * pattern (see SetHeightAndAlpha). 
+ * converts to
  *
- * The motion while an object is carried comes from fitting a spline through the
- * catch and throw points and maintaining the catch and throw velocities at
- * each end.  In the simplest case this boils down to cubic Bezier spline.  The
- * only wrinkle occurs when a ball is being carried for a long time.  The simple 
- * cubic spline maths produces a curve that goes miles away -- here we do a
- * bit of reparameterisation so things stay within sensible bounds.
- * (On a related note, this scheme is _much_ simpler than the Juggle Saver
- * one.  Juggle Saver achieves 2nd order continuity and much care is taken
- * to avoid spline ringing.)
- * 
- * The motion of the hands is identical to the ball carrying code. It uses two
- * splines: one while an object is being carried; and another when it moves from
- * the previous throw to the next catch.
+ * ...7717717717716615555555555...
+ *
+ * so the answer is to do a single 661 and then drop straight down to
+ * (5).
+ *
+ * [The number of balls in the generated pattern occasionally changes.
+ * In order to decrease the number of balls I had to introduce a new
+ * symbol into the Adam notation, [*] which means 'lose the current
+ * ball'.]
  */
-static const float CARRY_TIME = 0.56f;
-static const float PI = 3.14159265358979f;
 
+/* This code uses so many linked lists it's worth having a built-in
+ * leak-checker */
+#undef MEMTEST
 
-/* While a ball is thrown it twists slighty about an axis, this routine gives
- * the total about of twist for a given ball throw. */
-static float GetBallTwistAmount(const THROW_INFO* pThrow)
-{
-    if (pThrow->FromPos.x > pThrow->ToPos.x)
-        return 18.0f * powf(pThrow->TimeInAir, 1.5);
-    else
-        return -18.0f * powf(pThrow->TimeInAir, 1.5);
-}
+# define DEFAULTS      "*delay:        10000   \n" \
+                       "*count:        200     \n" \
+                       "*cycles:       1000    \n" \
+                       "*ncolors:      32      \n" \
+                        "*titleFont:  -*-helvetica-bold-r-normal-*-180-*\n" \
+                       "*showFPS:      False   \n" \
+                       "*wireframe:    False   \n" \
 
+# define refresh_juggle 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
 
-static float NormaliseAngle(float Ang)
-{
-    if (Ang >= 0.0f)
-    {
-        int i = (int) (Ang + 180.0f) / 360;
-        return Ang - 360.0f * i;
-    }
-    else
-    {
-        int i = (int)(180.0f - Ang) / 360;
-        return Ang + i * 360.0f;
-    }
-}
-
+#include "xlockmore.h"
+#include "sphere.h"
+#include "tube.h"
+#include "rotator.h"
+#include "gltrackball.h"
+#include "glxfonts.h"
+#include <ctype.h>
 
-/* The interpolate routine for ball carrying and hand motion.  We are given the
- * start (P0) and end (P1) points and the velocities at these points, the task
- * is to form a function P(t) such that:
- *    P(0) = P0
- *    P(TLen) = P1
- *    P'(0) = V0
- *    P'(TLen) = V1
- */
+#ifdef USE_GL /* whole file */
 
-static POS InterpolatePosition(
-    const POS* pP0, const POS* pV0, const POS* pP1, const POS* pV1,
-    float TLen, float t)
-{
-    POS p;
-    float a, b, c, d, tt, tc;
-    
-    /* The interpolation is based on a simple cubic that achieves 1st order
-     * continuity at the end points.  However the spline can become too long if
-     * the TLen parameter is large.  In this case we cap the curve's length (fix
-     * the shape) and then reparameterise time to achieve the continuity
-     * conditions. */
-
-    tc = CARRY_TIME;
-    
-    if (TLen > tc)
-    {
-        /* The reparameterisation tt(t) gives:
-         *  tt(0) = 0, tt(TLen) = tc, tt'(0) = 1, tt'(TLen) = 1
-         * and means we can set t = tt(t), TLen = tc and then fall through
-         * to use the normal cubic spline fit.
-         *    
-         * The reparameterisation is based on two piecewise quadratics, one
-         * that goes from t = 0 to t = TLen / 2 and the other, mirrored in
-         * tt and t that goes from t = TLen / 2 to t = TLen.
-         * Because TLen > tc we can arrange for tt to be unique in the range if
-         * we specify the quadratic in tt.  i.e. t = A * tt ^ 2 + B * tt + C.
-         *
-         * Considering the first piece and applying initial conditions.
-         *   tt = 0 when t = 0   =>  C = 0
-         *   tt' = 1 when t = 0   =>  B = 1
-         *   tt = tc / 2 when t = TLen / 2  =>  A = 2 * (TLen - tc) / tc^2
-         *
-         * writing in terms of t
-         *   tt = (-B + (B ^ 2 + 4At) ^ 0.5) / 2A
-         * or
-         *   tt = ((1 + 4At) ^ 0.5 - 1) / 2A */
-        
-        float A = 2.0f * (TLen - tc) / (tc * tc);
-        
-        if (t > TLen / 2.0f)
-            t = tc - (sqrtf(1.0f + 4.0f * A * (TLen - t)) - 1.0f) / (2.0f * A);
-        else
-            t = (sqrtf(1.0f + 4.0f * A * t) - 1.0f) / (2.0f * A);
-        
-        TLen = tc;
-    }
-    
-    /* The cubic spline takes the form:
-     *   P(t) = p0 * a(t) + v0 * b(t) + p1 * c(t) + v1 * d(t)
-     * where p0 is the start point, v0 the start velocity, p1 the end point and
-     * v1 the end velocity.  a(t), b(t), c(t) and d(t) are cubics in t.
-     * We can show that:
-     *
-     *  a(t) = 2 * (t / TLen) ^ 3 - 3 * (t / TLen) ^ 2 + 1
-     *  b(t) = t ^ 3 / TLen ^ 2 - 2 * t ^ 2 / TLen + t
-     *  c(t) = -2 * (t / TLen) ^ 3 + 3 * (t / TLen) ^ 2
-     *  d(t) = t ^ 3 / TLen ^ 2 - t ^ 2 / TLen
-     *
-     * statisfy the boundary conditions:
-     *    P(0) = p0, P(TLen) = p1, P'(0) = v0 and P'(TLen) = v1  */
-    
-    tt = t / TLen;
-    
-    a = tt * tt * (2.0f * tt - 3.0f) + 1.0f;
-    b = t * tt * (tt - 2.0f) + t;
-    c = tt * tt * (3.0f - 2.0f * tt);
-    d = t * tt * (tt - 1.0f);
-
-    p.x = a * pP0->x + b * pV0->x + c * pP1->x + d * pV1->x;
-    p.y = a * pP0->y + b * pV0->y + c * pP1->y + d * pV1->y;
-    p.z = a * pP0->z + b * pV0->z + c * pP1->z + d * pV1->z;
-
-    p.Rot = a * NormaliseAngle(pP0->Rot) + b * pV0->Rot + 
-        c * NormaliseAngle(pP1->Rot) + d * pV1->Rot;
-    p.Elev = a * NormaliseAngle(pP0->Elev) + b * pV0->Elev + 
-        c * NormaliseAngle(pP1->Elev) + d * pV1->Elev;
-
-    return p;
-}
+#define DEF_PATTERN "random" /* All patterns */
+#define DEF_TAIL "1" /* No trace */
+#ifdef UNI
+/* Maybe a ROLA BOLA would be at a better angle for viewing */
+#define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
+#endif
+#define DEF_REAL "True"
+#define DEF_DESCRIBE "True"
+
+#define DEF_BALLS "True" /* Use Balls */
+#define DEF_CLUBS "True" /* Use Clubs */
+#define DEF_TORCHES "True" /* Use Torches */
+#define DEF_KNIVES "True" /* Use Knives */
+#define DEF_RINGS "True" /* Use Rings */
+#define DEF_BBALLS "True" /* Use Bowling Balls */
+
+static char *pattern;
+static int tail;
+#ifdef UNI
+static Bool uni;
+#endif
+static Bool real;
+static Bool describe;
+static Bool balls;
+static Bool clubs;
+static Bool torches;
+static Bool knives;
+static Bool rings;
+static Bool bballs;
+static char *only;
+
+static XrmOptionDescRec opts[] = {
+  {"-pattern",  ".juggle.pattern",  XrmoptionSepArg, NULL  },
+  {"-tail",     ".juggle.tail",     XrmoptionSepArg, NULL  },
+#ifdef UNI
+  {"-uni",      ".juggle.uni",      XrmoptionNoArg,  "on"  },
+  {"+uni",      ".juggle.uni",      XrmoptionNoArg,  "off" },
+#endif
+  {"-real",     ".juggle.real",     XrmoptionNoArg,  "on"  },
+  {"+real",     ".juggle.real",     XrmoptionNoArg,  "off" },
+  {"-describe", ".juggle.describe", XrmoptionNoArg,  "on"  },
+  {"+describe", ".juggle.describe", XrmoptionNoArg,  "off" },
+  {"-balls",    ".juggle.balls",    XrmoptionNoArg,  "on"  },
+  {"+balls",    ".juggle.balls",    XrmoptionNoArg,  "off" },
+  {"-clubs",    ".juggle.clubs",    XrmoptionNoArg,  "on"  },
+  {"+clubs",    ".juggle.clubs",    XrmoptionNoArg,  "off" },
+  {"-torches",  ".juggle.torches",  XrmoptionNoArg,  "on"  },
+  {"+torches",  ".juggle.torches",  XrmoptionNoArg,  "off" },
+  {"-knives",   ".juggle.knives",   XrmoptionNoArg,  "on"  },
+  {"+knives",   ".juggle.knives",   XrmoptionNoArg,  "off" },
+  {"-rings",    ".juggle.rings",    XrmoptionNoArg,  "on"  },
+  {"+rings",    ".juggle.rings",    XrmoptionNoArg,  "off" },
+  {"-bballs",   ".juggle.bballs",   XrmoptionNoArg,  "on"  },
+  {"+bballs",   ".juggle.bballs",   XrmoptionNoArg,  "off" },
+  {"-only",     ".juggle.only",     XrmoptionSepArg, NULL  },
+};
 
+static argtype vars[] = {
+  { &pattern,  "pattern",  "Pattern",  DEF_PATTERN,  t_String },
+  { &tail,     "tail",     "Tail",     DEF_TAIL,     t_Int    },
+#ifdef UNI
+  { &uni,      "uni",      "Uni",      DEF_UNI,      t_Bool   },
+#endif
+  { &real,     "real",     "Real",     DEF_REAL,     t_Bool   },
+  { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool   },
+  { &balls,    "balls",    "Clubs",    DEF_BALLS,    t_Bool   },
+  { &clubs,    "clubs",    "Clubs",    DEF_CLUBS,    t_Bool   },
+  { &torches,  "torches",  "Torches",  DEF_TORCHES,  t_Bool   },
+  { &knives,   "knives",   "Knives",   DEF_KNIVES,   t_Bool   },
+  { &rings,    "rings",    "Rings",    DEF_RINGS,    t_Bool   },
+  { &bballs,   "bballs",   "BBalls",   DEF_BBALLS,   t_Bool   },
+  { &only,     "only",     "BBalls",   " ",          t_String },
+};
 
-static POS InterpolateCarry(
-    const THROW_INFO* pThrow, const THROW_INFO* pNext, float t)
+static OptionStruct desc[] =
 {
-    float CT = CARRY_TIME + pThrow->TotalTime - pThrow->TimeInAir;
-    return InterpolatePosition(&pThrow->ToPos, &pThrow->ToVelocity,
-        &pNext->FromPos, &pNext->FromVelocity, CT, t);
-}
+  { "-pattern string", "Cambridge Juggling Pattern" },
+  { "-tail num",       "Trace Juggling Patterns" },
+#ifdef UNI
+  { "-/+uni",          "Unicycle" },
+#endif
+  { "-/+real",         "Real-time" },
+  { "-/+describe",     "turn on/off pattern descriptions." },
+  { "-/+balls",        "turn on/off Balls." },
+  { "-/+clubs",        "turn on/off Clubs." },
+  { "-/+torches",      "turn on/off Flaming Torches." },
+  { "-/+knives",       "turn on/off Knives." },
+  { "-/+rings",        "turn on/off Rings." },
+  { "-/+bballs",       "turn on/off Bowling Balls." },
+  { "-only",           "Turn off all objects but the named one." },
+};
 
+ENTRYPOINT ModeSpecOpt juggle_opts =
+ {countof(opts), opts, countof(vars), vars, desc};
+
+
+/* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480.  All
+   "thicknesses" are scaled by sqrt(sp->scale) so that they are
+   proportionally thicker for smaller windows.  Objects spinning out
+   of the plane (such as clubs) fake perspective by compressing their
+   horizontal coordinates by PERSPEC  */
+
+/* Figure */
+#define ARMLENGTH 50
+#define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
+#define POSE 10
+#define BALLRADIUS ARMWIDTH
+
+/* build all the models assuming a 480px high scene */
+#define SCENE_HEIGHT 480
+#define SCENE_WIDTH ((int)(SCENE_HEIGHT*(MI_WIDTH(mi)/(float)MI_HEIGHT(mi))))
+
+/*#define PERSPEC  0.4*/
+
+/* macros */
+#define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
+
+/* Timing based on count.  Units are milliseconds.  Juggles per second
+       is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
+
+#define THROW_CATCH_INTERVAL (sp->count)
+#define THROW_NULL_INTERVAL  (sp->count * 0.5)
+#define CATCH_THROW_INTERVAL (sp->count * 0.2)
+
+/********************************************************************
+ * Trace Definitions                                                *
+ *                                                                  *
+ * These record rendering data so that a drawn object can be erased *
+ * later.  Each object has its own Trace list.                      *
+ *                                                                  *
+ ********************************************************************/
+
+typedef struct {double x, y; } DXPoint;
+typedef struct trace *TracePtr;
+typedef struct trace {
+  TracePtr next, prev;
+  double x, y;
+  double angle;
+  int divisions;
+  DXPoint dlast;
+#ifdef MEMTEST
+  char pad[1024];
+#endif
+} Trace;
+
+/*******************************************************************
+ * Object Definitions                                              *
+ *                                                                 *
+ * These describe the various types of Object that can be juggled  *
+ *                                                                 *
+ *******************************************************************/
+typedef int (DrawProc)(ModeInfo*, unsigned long, Trace *);
+
+static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
+static DrawProc show_ring, show_bball;
+
+typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
+              NUM_OBJECT_TYPES} ObjType;
+
+#define OBJMIXPROB 20   /* inverse of the chances of using an odd
+                                                  object in the pattern */
+
+static const GLfloat body_color_1[4] = { 0.9, 0.7, 0.5, 1 };
+static const GLfloat body_color_2[4] = { 0.6, 0.4, 0.2, 1 };
+
+static const struct {
+  DrawProc *draw;                           /* Object Rendering function */
+  int       handle;                         /* Length of object's handle */
+  int       mintrail;                            /* Minimum trail length */
+  double    cor;      /* Coefficient of Restitution.  perfect bounce = 1 */
+  double    weight;          /* Heavier objects don't get thrown as high */
+} ObjectDefs[] = {
+  { /* Ball */
+       show_ball,
+       0,
+       1,
+       0.9,
+       1.0,
+  },
+  { /* Club */
+       show_europeanclub,
+       15,
+       1,
+       0.55, /* Clubs don't bounce too well */
+       1.0,
+  },
+  { /* Torch */
+       show_torch,
+       15,
+       20, /* Torches need flames */
+       0, /* Torches don't bounce -- fire risk! */
+       1.0,
+  },
+  { /* Knife */
+       show_knife,
+       15,
+       1,
+       0, /* Knives don't bounce */
+       1.0,
+  },
+  { /* Ring */
+       show_ring,
+       15,
+       1,
+       0.8,
+       1.0,
+  },
+  { /* Bowling Ball */
+       show_bball,
+       0,
+       1,
+       0.2,
+       5.0,
+  },
+};
 
-/* Determine the position of the hand at a point in time. */
+/**************************
+ * Trajectory definitions *
+ **************************/
+
+typedef enum {HEIGHT, ADAM} Notation;
+typedef enum {Empty, Full, Ball} Throwable;
+typedef enum {LEFT, RIGHT} Hand;
+typedef enum {THROW, CATCH} Action;
+typedef enum {HAND, ELBOW, SHOULDER} Joint;
+typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
+                         PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
+typedef struct {double a, b, c, d; } Spline;
+typedef DXPoint Arm[3];
+
+
+/* Object is an arbitrary object being juggled.  Each Trajectory
+ * references an Object ("count" tracks this), and each Object is also
+ * linked into a global Objects list.  Objects may include a Trace
+ * list for tracking erasures. */
+typedef struct object *ObjectPtr;
+typedef struct object {
+  ObjectPtr next, prev;
+
+  ObjType type;
+  int     color;
+  int     count; /* reference count */
+  Bool    active; /* Object is in use */
+
+  Trace  *trace;
+  int     tracelen;
+  int     tail;
+#ifdef MEMTEST
+  char pad[1024];
+#endif
+} Object;
+
+/* Trajectory is a segment of juggling action.  A list of Trajectories
+ * defines the juggling performance.  The Trajectory list goes through
+ * multiple processing steps to convert it from basic juggling
+ * notation into rendering data. */
+
+typedef struct trajectory *TrajectoryPtr;
+typedef struct trajectory {
+  TrajectoryPtr prev, next;  /* for building list */
+  TrajectoryStatus status;
+
+  /* Throw */
+  char posn;
+  int height;
+  int adam;
+  char *pattern;
+  char *name;
+
+  /* Action */
+  Hand hand;
+  Action action;
+
+  /* LinkedAction */
+  int color;
+  Object *object;
+  int divisions;
+  double angle, spin;
+  TrajectoryPtr balllink;
+  TrajectoryPtr handlink;
+
+  /* PThratch */
+  double cx; /* Moving juggler */
+  double x, y; /* current position */
+  double dx, dy; /* initial velocity */
+
+  /* Predictor */
+  Throwable type;
+  unsigned long start, finish;
+  Spline xp, yp;
+
+#ifdef MEMTEST
+  char pad[1024];
+#endif
+} Trajectory;
+
+
+/*******************
+ * Pattern Library *
+ *******************/
+
+typedef struct {
+  const char * pattern;
+  const char * name;
+} patternstruct;
+
+/* List of popular patterns, in any order */
+/* Patterns should be given in Adam notation so the generator can
+   concatenate them safely.  Null descriptions are ok.  Height
+   notation will be displayed automatically.  */
+/* Can't const this because it is qsorted.  This *should* be reentrant,
+   I think... */
+static /*const*/ patternstruct portfolio[] = {
+  {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
+  {"[2 0]", /* 4 0 */ "2 in 1 hand"},
+  {"[2 0 1]", /* 5 0 1 */},
+  {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
+  {"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
+  {"[2 0 1 1]", /* 6 0 1 1 */},
+
+  {"[3]", /* 3 */ "3 cascade"},
+  {"[+3]", /* +3 */ "reverse 3 cascade"},
+  {"[=3]", /* =3 */ "cascade 3 under arm"},
+  {"[&3]", /* &3 */ "cascade 3 catching under arm"},
+  {"[_3]", /* _3 */ "bouncing 3 cascade"},
+  {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
+  {"[3 2 1]", /* 5 3 1" */},
+  {"[3 3 1]", /* 4 4 1" */},
+  {"[3 1 2]", /* 6 1 2 */ "See-saw"},
+  {"[=3 3 1 2]", /* =4 5 1 2 */},
+  {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
+  {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
+  {"[3 3 1]", /* 4 4 1 */},
+  {"[+3 2 3]", /* +4 2 3 */},
+  {"[+3 1]", /* +5 1 */ "3 shower"},
+  {"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
+  {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
+  {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
+  {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
+  {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
+  {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
+  {"[3 2 2 0 3 2 0 2 3 0 2 2 0]", /* 7 3 3 0 7 3 0 3 7 0 3 3 0 */},
+  {"[3 0 2 0]", /* 8 0 4 0 */},
+  {"[_3 2 1]", /* _5 3 1 */},
+  {"[_3 0 1]", /* _8 0 1 */},
+  {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
+  {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
+
+  {"[4]", /* 4 */ "4 cascade"},
+  {"[+4 3]", /* +5 3 */ "4 ball half shower"},
+  {"[4 4 2]", /* 5 5 2 */},
+  {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
+  {"[+4 3 +4]", /* +5 3 +4 */},
+  {"[4 3 4 4]", /* 5 3 4 4 */},
+  {"[4 3 3 4]", /* 6 3 3 4 */},
+  {"[4 3 2 4", /* 6 4 2 4 */},
+  {"[+4 1]", /* +7 1 */ "4 shower"},
+  {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
+  {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
+  {"[+4 2 1 3]", /* +9 3 1 3 */},
+  {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
+  {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
+  {"[_4 3 3]", /* _6 3 3 */},
+  {"[_4 3 1]", /* _7 4 1 */},
+  {"[_4 2 1]", /* _8 3 1 */},
+  {"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
+  {"[_4 1 3 1]", /* _9 1 5 1 */},
+  {"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
+
+  {"[5]", /* 5 */ "5 cascade"},
+  {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
+  {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
+  {"[5 4 4]", /* 7 4 4 */},
+  {"[_5 4 4]", /* _7 4 4 */},
+  {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
+  {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
+  {"[_5 4 1 +4]", /* _9 5 1 5 */},
+  {"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
+  {"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
+  {"[_5 4 4 5 1]",},
+  {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
+
+  {"[6]", /* 6 */ "6 cascade"},
+  {"[+6 5]", /* +7 5 */},
+  {"[6 4]", /* 8 4 */},
+  {"[+6 3]", /* +9 3 */},
+  {"[6 5 4 4]", /* 9 7 4 4 */},
+  {"[+6 5 5 5]", /* +9 5 5 5 */},
+  {"[6 0 6]", /* 9 0 9 */},
+  {"[_6 0 _6]", /* _9 0 _9 */},
+
+  {"[_7]", /* _7 */ "bouncing 7 cascade"},
+  {"[7]", /* 7 */ "7 cascade"},
+  {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
 
-static void GetHandPosition(
-    PATTERN_INFO* pPattern, int RightHand, float Time, POS* pPos)
-{
-    OBJECT_POSITION* pObj = 
-        RightHand == 0 ? &pPattern->LeftHand : &pPattern->RightHand;
-    THROW_INFO* pLastThrow;
-    
-    /* Upon entry, the throw information for the relevant hand may be out of
-     * sync.  Therefore we advance through the pattern if required. */
-
-    while (pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand + pObj->TimeOffset 
-        <= (int) Time)
-    {
-        int w = pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand;
-        pObj->TimeOffset += w;
-        pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
-    }
+};
 
-    pLastThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
 
-    /* The TimeInAir will only ever be 2 or 0 if no object is ever thrown by
-     * this hand.  In normal circumstances, 2's in the site swap are coalesced
-     * and added to TotalTime of the previous throw.  0 is a hole and means that
-     * an object isn't there.  In this case we just hold the hand still. */
-    if (pLastThrow->TimeInAir == 2 || pLastThrow->TimeInAir == 0)
-    {
-        pPos->x = pLastThrow->FromPos.x;
-        pPos->y = pLastThrow->FromPos.y;
-    }
-    else
-    {
-        /* The hand is either moving to catch the next object or carrying the
-         * next object to its next throw position.  The way THROW_INFO is
-         * structured means the relevant information for the object we're going
-         * to catch is held at the point at which it was thrown 
-         * (pNextThrownFrom).  We can't go straight for it and instead have to
-         * look at the object we've about to throw next and work out where it
-         * came from. */
-        
-        THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
-            (pObj->ThrowIndex + pLastThrow->NextForHand) % pPattern->ThrowLen];
-        
-        THROW_INFO* pNextThrownFrom = 
-            &pPattern->pThrowInfo[pNextThrow->PrevThrow];
-        
-        /* tc is a measure of how long the object we're due to catch is being
-         * carried for.  We use this to work out if we've actually caught it at
-         * this moment in time. */
-        
-        float tc = CARRY_TIME + 
-            pNextThrownFrom->TotalTime - pNextThrownFrom->TimeInAir;
-        
-        Time -= pObj->TimeOffset;
 
-        if (Time > pLastThrow->NextForHand - tc)
-        {
-            /* carrying this ball to it's new location */
-            *pPos = InterpolateCarry(pNextThrownFrom,
-                pNextThrow, (Time - (pLastThrow->NextForHand - tc)));
-        }
-        else
-        {
-            /* going for next catch */
-            *pPos = InterpolatePosition(
-                &pLastThrow->FromPos, &pLastThrow->FromVelocity, 
-                &pNextThrownFrom->ToPos, &pNextThrownFrom->ToVelocity,
-                pLastThrow->NextForHand - tc, Time);
-        }
-    }
-}
+typedef struct { int start; int number; } PatternIndex;
 
+struct patternindex {
+  int minballs;
+  int maxballs;
+  PatternIndex index[countof(portfolio)];
+};
 
-static float SinDeg(float AngInDegrees)
-{
-    return sinf(AngInDegrees * PI / 180.0f);
+
+/* Jugglestruct: per-screen global data.  The master Object
+ * and Trajectory lists are anchored here. */
+typedef struct {
+  GLXContext *glx_context;
+  rotator *rot;
+  trackball_state *trackball;
+  Bool button_down_p;
+
+  double        scale;
+  double        cx;
+  double        Gr;
+  Trajectory   *head;
+  Arm           arm[2][2];
+  char         *pattern;
+  int           count;
+  int           num_balls;
+  time_t        begintime; /* should make 'time' usable for at least 48 days
+                                                       on a 32-bit machine */
+  unsigned long time; /* millisecond timer*/
+  ObjType       objtypes;
+  Object       *objects;
+  struct patternindex patternindex;
+
+  XFontStruct *mode_font;
+  GLuint font_dlist;
+} jugglestruct;
+
+static jugglestruct *juggles = (jugglestruct *) NULL;
+
+/*******************
+ * list management *
+ *******************/
+
+#define DUP_OBJECT(n, t) { \
+  (n)->object = (t)->object; \
+  if((n)->object != NULL) (n)->object->count++; \
 }
 
+/* t must point to an existing element.  t must not be an
+   expression ending ->next or ->prev */
+#define REMOVE(t) { \
+  (t)->next->prev = (t)->prev; \
+  (t)->prev->next = (t)->next; \
+  free(t); \
+}
 
-static float CosDeg(float AngInDegrees)
+/* t receives element to be created and added to the list.  ot must
+   point to an existing element or be identical to t to start a new
+   list. Applicable to Trajectories, Objects and Traces. */
+#define ADD_ELEMENT(type, t, ot) \
+  if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
+    (t)->next = (ot)->next; \
+    (t)->prev = (ot); \
+    (ot)->next = (t); \
+    (t)->next->prev = (t); \
+  }
+
+static void
+object_destroy(Object* o)
 {
-    return cosf(AngInDegrees * PI / 180.0f);
+  if(o->trace != NULL) {
+       while(o->trace->next != o->trace) {
+         Trace *s = o->trace->next;
+         REMOVE(s); /* Don't eliminate 's' */
+       }
+       free(o->trace);
+  }
+  REMOVE(o);
 }
 
+static void
+trajectory_destroy(Trajectory *t) {
+  if(t->name != NULL) free(t->name);
+  if(t->pattern != NULL) free(t->pattern);
+  /* Reduce object link count and call destructor if necessary */
+  if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
+       object_destroy(t->object);
+  }
+  REMOVE(t); /* Unlink and free */
+}
 
-/* Offset the specified position to get the centre of the object based on the
- * the handle length and the current orientation */
+static void
+free_juggle(jugglestruct *sp) {
+  if (sp->head != NULL) {
+       while (sp->head->next != sp->head) {
+         trajectory_destroy(sp->head->next);
+       }
+       free(sp->head);
+       sp->head = (Trajectory *) NULL;
+  }
+  if(sp->objects != NULL) {
+       while (sp->objects->next != sp->objects) {
+         object_destroy(sp->objects->next);
+       }
+       free(sp->objects);
+       sp->objects = (Object*)NULL;
+  }
+  if(sp->pattern != NULL) {
+       free(sp->pattern);
+       sp->pattern = NULL;
+  }
+  if (sp->mode_font!=None) {
+       XFreeFontInfo(NULL,sp->mode_font,1);
+       sp->mode_font = None;
+  }
+}
 
-static void OffsetHandlePosition(const POS* pPos, float HandleLen, POS* pResult)
+static Bool
+add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
 {
-    pResult->x = pPos->x + HandleLen * SinDeg(pPos->Rot) * CosDeg(pPos->Elev);
-    pResult->y = pPos->y + HandleLen * SinDeg(pPos->Elev);
-    pResult->z = pPos->z + HandleLen * CosDeg(pPos->Rot) * CosDeg(pPos->Elev);
-    pResult->Elev = pPos->Elev;
-    pResult->Rot = pPos->Rot;
+  Trajectory *t;
+
+  ADD_ELEMENT(Trajectory, t, sp->head->prev);
+  if(t == NULL){ /* Out of Memory */
+       free_juggle(sp);
+       return False;
+  }
+  t->object = NULL;
+  if(name != NULL)
+       t->name = strdup(name);
+  t->posn = type;
+  if (n == ADAM) {
+       t->adam = h;
+       t->height = 0;
+       t->status = ATCH;
+  } else {
+       t->height = h;
+       t->status = THRATCH;
+  }
+  return True;
 }
 
-
-static void GetObjectPosition(
-    PATTERN_INFO* pPattern, int Obj, float Time, float HandleLen, POS* pPos)
+/* add a Thratch to the performance */
+static Bool
+program(ModeInfo *mi, const char *patn, const char *name, int cycles)
 {
-    OBJECT_POSITION* pObj = &pPattern->pObjectInfo[Obj];
-    THROW_INFO* pThrow;
-    
-    /* Move through the pattern, if required, such that pThrow corresponds to
-     * the current throw for this object. */
-
-    while (pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime + pObj->TimeOffset
-        <= (int) Time)
-    {
-        int w = pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime;
-        pObj->TimeOffset += w;
-        pObj->TotalTwist = NormaliseAngle(pObj->TotalTwist + 
-            GetBallTwistAmount(&pPattern->pThrowInfo[pObj->ThrowIndex]));
-        
-        pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
-    }
-
-    pThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  const char *p;
+  int w, h, i, seen;
+  Notation notation;
+  char type;
+
+  if (MI_IS_VERBOSE(mi)) {
+       (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
+                                  MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
+  }
+
+  for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
+                                                                               have been programmed */
+       /* title is the pattern name to be supplied to the first throw of
+          a sequence.  If no name if given, use an empty title so that
+          the sequences are still delimited. */
+       const char *title = (name != NULL)? name : "";
+       type=' ';
+       h = 0;
+       seen = 0;
+       notation = HEIGHT;
+       for(p=patn; *p; p++) {
+         if (*p >= '0' && *p <='9') {
+               seen = 1;
+               h = 10*h + (*p - '0');
+         } else {
+               Notation nn = notation;
+               switch (*p) {
+               case '[':            /* begin Adam notation */
+                 notation = ADAM;
+                 break;
+               case '-':            /* Inside throw */
+                 type = ' ';
+                 break;
+               case '+':            /* Outside throw */
+               case '=':            /* Cross throw */
+               case '&':            /* Cross catch */
+               case 'x':            /* Cross throw and catch */
+               case '_':            /* Bounce */
+               case 'k':            /* Kickup */
+                 type = *p;
+                 break;
+               case '*':            /* Lose ball */
+                 seen = 1;
+                 h = -1;
+                 /* fall through */
+               case ']':             /* end Adam notation */
+                 nn = HEIGHT;
+                 /* fall through */
+               case ' ':
+                 if (seen) {
+                       i++;
+                       if (!add_throw(sp, type, h, notation, title))
+                               return False;
+                       title = NULL;
+                       type=' ';
+                       h = 0;
+                       seen = 0;
+                 }
+                 notation = nn;
+                 break;
+               default:
+                 if(w == 0) { /* Only warn on first pass */
+                       (void) fprintf(stderr,
+                                                  "juggle[%d]: Unexpected pattern instruction: '%c'\n",
+                                                  MI_SCREEN(mi), *p);
+                 }
+                 break;
+               }
+         }
+       }
+       if (seen) { /* end of sequence */
+         if (!add_throw(sp, type, h, notation, title))
+               return False;
+         title = NULL;
+       }
+  }
+  return True;
+}
 
-    if (pThrow->TimeInAir == 2 || pThrow->TimeInAir == 0)
-    {
-        *pPos = pThrow->FromPos;
-        OffsetHandlePosition(pPos, HandleLen, pPos);
-    }
-    else
-    {
-        float tc = pThrow->TimeInAir - CARRY_TIME;
-        float BallTwist = GetBallTwistAmount(pThrow);
-        Time -= pObj->TimeOffset;
-        if (Time < tc)
-        {
-            /* object in air */
-            POS From, To;
-            float t, b;
-
-            t = Time / tc;
-            
-            OffsetHandlePosition(&pThrow->FromPos, HandleLen, &From);
-            OffsetHandlePosition(&pThrow->ToPos, HandleLen, &To);
-
-            b = (To.y - From.y) / tc + pPattern->Alpha * tc;
-            
-            pPos->x = (1.0f - t) * From.x + t * To.x;
-            pPos->z = (1.0f - t) * From.z + t * To.z;
-            pPos->y = -pPattern->Alpha * Time * Time + b * Time + From.y;
-            
-            if (pObj->ObjectType == OBJECT_BALL)
-                pPos->Rot = pObj->TotalTwist + t * BallTwist;
-            else
-            {
-                /* We describe the rotation of a club (or ring) with an
-                 * elevation and rotation but don't include a twist.
-                 * If we ignore twist for the moment, the orientation at a
-                 * rotation of r and an elevation of e can be also be expressed
-                 * by rotating the object a further 180 degrees and sort of
-                 * mirroring the rotation, e.g.:
-                 *    rot = r + 180 and elev = 180 - e
-                 * We can easily show that the maths holds, consider the
-                 * x, y ,z position of the end of a unit length club.
-                 *    y = sin(180 - e) = sin(e)
-                 *    x = cos(180 - e) * sin(r + 180) = -cos(e) * - sin(r)
-                 *    z = cos(180 - e) * cos(r + 180) = -cos(e) * - cos(r)
-                 * When a club is thrown these two potential interpretations
-                 * can produce unexpected results.
-                 * The approach we adopt is that we try and minimise the amount
-                 * of rotation we give a club -- normally this is what happens
-                 * when juggling since it's much easier to spin the club.
-                 *
-                 * When we come to drawing the object the two interpretations
-                 * aren't identical, one causes the object to twist a further
-                 * 180 about its axis.  We avoid the issue by ensuring our
-                 * objects have rotational symmetry of order 2 (e.g. we make
-                 * sure clubs have an even number of stripes) this makes the two
-                 * interpretations appear identical. */
-
-                float RotAmt = NormaliseAngle(To.Rot - From.Rot);
-
-                if (RotAmt < -90.0f)
-                {
-                    To.Elev += 180  - 2 * NormaliseAngle(To.Elev);
-                    RotAmt += 180.0f;
-                }
-                else if (RotAmt > 90.0f)
-                {
-                    To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
-                    RotAmt -= 180.0f;
-                }
-
-                pPos->Rot = From.Rot + t * RotAmt;
-            }
+/*
+ ~~~~\~~~~~\~~~
+ \\~\\~\~\\\~~~
+ \\~\\\\~\\\~\~
+ \\\\\\\\\\\~\\
 
-            pPos->Elev = (1.0f - t) * From.Elev + t * To.Elev;
+[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
 
-        }
-        else
-        {
-            THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
-                   (pObj->ThrowIndex + pThrow->TotalTime) % pPattern->ThrowLen];
+4 4 1 3 12 2 4 1 4 4 13 0 3 1
 
-            *pPos = InterpolateCarry(pThrow, pNextThrow, Time - tc);
+*/
+#define BOUNCEOVER 10
+#define KICKMIN 7
+#define THROWMAX 20
 
-            if (pObj->ObjectType == OBJECT_BALL)
-                pPos->Rot = pObj->TotalTwist + BallTwist;
+/* Convert Adam notation into heights */
+static void
+adam(jugglestruct *sp)
+{
+  Trajectory *t, *p;
+  for(t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status == ATCH) {
+         int a = t->adam;
+         t->height = 0;
+         for(p = t->next; a > 0; p = p->next) {
+               if(p == sp->head) {
+                 t->height = -9; /* Indicate end of processing for name() */
+                 return;
+               }
+               if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
+                 a--;
+               }
+               t->height++;
+         }
+         if(t->height > BOUNCEOVER && t->posn == ' '){
+               t->posn = '_'; /* high defaults can be bounced */
+         } else if(t->height < 3 && t->posn == '_') {
+               t->posn = ' '; /* Can't bounce short throws. */
+         }
+         if(t->height < KICKMIN && t->posn == 'k'){
+               t->posn = ' '; /* Can't kick short throws */
+         }
+         if(t->height > THROWMAX){
+               t->posn = 'k'; /* Use kicks for ridiculously high throws */
+         }
+         t->status = THRATCH;
+       }
+  }
+}
 
-            OffsetHandlePosition(pPos, HandleLen, pPos);
-        }
-    }
+/* Discover converted heights and update the sequence title */
+static void
+name(jugglestruct *sp)
+{
+  Trajectory *t, *p;
+  char buffer[BUFSIZ];
+  char *b;
+  for(t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status == THRATCH && t->name != NULL) {
+         b = buffer;
+         for(p = t; p == t || p->name == NULL; p = p->next) {
+               if(p == sp->head || p->height < 0) { /* end of reliable data */
+                 return;
+               }
+               if(p->posn == ' ') {
+                 b += sprintf(b, " %d", p->height);
+               } else {
+                 b += sprintf(b, " %c%d", p->posn, p->height);
+               }
+               if(b - buffer > 500) break; /* otherwise this could eventually
+                                                                          overflow.  It'll be too big to
+                                                                          display anyway. */
+         }
+         if(*t->name != 0) {
+               (void) sprintf(b, ", %s", t->name);
+         }
+         free(t->name); /* Don't need name any more, it's been converted
+                                               to pattern */
+         t->name = NULL;
+         if(t->pattern != NULL) free(t->pattern);
+         t->pattern = strdup(buffer);
+       }
+  }
 }
 
+/* Split Thratch notation into explicit throws and catches.
+   Usually Catch follows Throw in same hand, but take care of special
+   cases. */
 
-/* Alpha is used to represent the acceleration due to gravity (in fact
- * 2 * Alpha is the acceleration).  Alpha is adjusted according to the pattern
- * being juggled.  My preference is to slow down patterns with lots of objects
- * -- they move too fast in realtime.  Also I prefer to see a balance between
- * the size of the figure and the height of objects thrown -- juggling patterns
- * with large numbers of objects under real gravity can mean balls are lobbed
- * severe heights.  Adjusting Alpha achieves both these goals.
- *
- * Basically we pick a height we'd like to see the biggest throw reach and then
- * adjust Alpha to meet this. */
+/* ..n1.. -> .. LTn RT1 LC RC .. */
+/* ..nm.. -> .. LTn LC RTm RC .. */
 
-static void SetHeightAndAlpha(PATTERN_INFO* pPattern, 
-    const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
+static Bool
+part(jugglestruct *sp)
 {
-    float H;
-    int MaxW = 5;
-    int i;
-    
-    if (Site != NULL)
-    {
-        for (i = 0; i < Len; i++)
-            MaxW = max(MaxW, Site[i]);
-    }
-    else
-    {
-        for (i = 0; i < Len; i++)
-            MaxW = max(MaxW, pExtInfo[i].Weight);
-    }
-    
-    /* H is the ideal max height we'd like our objects to reach.  The formula
-     * was developed by trial and error and was simply stolen from Juggle Saver.
-     * Alpha is then calculated from the classic displacement formula:
-     *   s = 0.5at^2 + ut  (where a = 2 * Alpha)
-     * We know u (the velocity) is zero at the peak, and the object should fall
-     * H units in half the time of biggest throw weight.
-     * Finally we determine the proper height the max throw reaches since this
-     * may not be H because capping may be applied (e.g. for max weights less
-     * than 5). */
-    
-    H = 8.0f * powf(MaxW / 2.0f, 0.8f) + 5.0f;
-    pPattern->Alpha = (2.0f * H) / powf(max(5, MaxW) - CARRY_TIME, 2.0f);
-    pPattern->Height = pPattern->Alpha * powf((MaxW - CARRY_TIME) * 0.5f, 2);
+  Trajectory *t, *nt, *p;
+  Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
+
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status > THRATCH) {
+         hand = t->hand;
+       } else if (t->status == THRATCH) {
+         char posn = '=';
+
+         /* plausibility check */
+         if (t->height <= 2 && t->posn == '_') {
+               t->posn = ' '; /* no short bounces */
+         }
+         if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
+               t->posn = ' '; /* 1's need close catches */
+         }
+
+         switch (t->posn) {
+                 /*         throw          catch    */
+         case ' ': posn = '-'; t->posn = '+'; break;
+         case '+': posn = '+'; t->posn = '-'; break;
+         case '=': posn = '='; t->posn = '+'; break;
+         case '&': posn = '+'; t->posn = '='; break;
+         case 'x': posn = '='; t->posn = '='; break;
+         case '_': posn = '_'; t->posn = '-'; break;
+         case 'k': posn = 'k'; t->posn = 'k'; break;
+         default:
+               (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
+               break;
+         }
+         hand = (Hand) ((hand + 1) % 2);
+         t->status = ACTION;
+         t->hand = hand;
+         p = t->prev;
+
+         if (t->height == 1 && p != sp->head) {
+               p = p->prev; /* '1's are thrown earlier than usual */
+         }
+
+
+
+         t->action = CATCH;
+         ADD_ELEMENT(Trajectory, nt, p);
+         if(nt == NULL){
+               free_juggle(sp);
+               return False;
+         }
+         nt->object = NULL;
+         nt->status = ACTION;
+         nt->action = THROW;
+         nt->height = t->height;
+         nt->hand = hand;
+         nt->posn = posn;
+
+       }
+  }
+  return True;
 }
 
+static ObjType
+choose_object(void) {
+  ObjType o;
+  for (;;) {
+       o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
+       if(balls && o == BALL) break;
+       if(clubs && o == CLUB) break;
+       if(torches && o == TORCH) break;
+       if(knives && o == KNIFE) break;
+       if(rings && o == RING) break;
+       if(bballs && o == BBALLS) break;
+  }
+  return o;
+}
 
-/* Where positions and spin info is not specified, generate suitable default
* values. */
+/* Connnect up throws and catches to figure out which ball goes where.
  Do the same with the juggler's hands. */
 
-static int GetDefaultSpins(int Weight)
+static void
+lob(ModeInfo *mi)
 {
-    if (Weight < 3)
-        return 0;
-    else if (Weight < 4)
-        return 1;
-    else if (Weight < 7)
-        return 2;
-    else
-        return 3;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  Trajectory *t, *p;
+  int h;
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status == ACTION) {
+         if (t->action == THROW) {
+               if (t->type == Empty) {
+                 /* Create new Object */
+                 ADD_ELEMENT(Object, t->object, sp->objects);
+                 t->object->count = 1;
+                 t->object->tracelen = 0;
+                 t->object->active = False;
+                 /* Initialise object's circular trace list */
+                 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
+
+                 if (MI_NPIXELS(mi) > 2) {
+                       t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
+                 } else {
+#ifdef STANDALONE
+                       t->object->color = 1;
+#else
+                       t->object->color = 0;
+#endif
+                 }
+
+                 /* Small chance of picking a random object instead of the
+                        current theme. */
+                 if(NRAND(OBJMIXPROB) == 0) {
+                       t->object->type = choose_object();
+                 } else {
+                       t->object->type = sp->objtypes;
+                 }
+
+                 /* Check to see if we need trails for this object */
+                 if(tail < ObjectDefs[t->object->type].mintrail) {
+                       t->object->tail = ObjectDefs[t->object->type].mintrail;
+                 } else {
+                       t->object->tail = tail;
+                 }
+               }
+
+               /* Balls can change divisions at each throw */
+                /* no, that looks stupid. -jwz */
+                if (t->divisions < 1)
+                  t->divisions = 2 * (NRAND(2) + 1);
+
+               /* search forward for next catch in this hand */
+               for (p = t->next; t->handlink == NULL; p = p->next) {
+                 if(p->status < ACTION || p == sp->head) return;
+                 if (p->action == CATCH) {
+                       if (t->handlink == NULL && p->hand == t->hand) {
+                         t->handlink = p;
+                       }
+                 }
+               }
+
+               if (t->height > 0) {
+                 h = t->height - 1;
+
+                 /* search forward for next ball catch */
+                 for (p = t->next; t->balllink == NULL; p = p->next) {
+                       if(p->status < ACTION || p == sp->head) {
+                         t->handlink = NULL;
+                         return;
+                       }
+                       if (p->action == CATCH) {
+                         if (t->balllink == NULL && --h < 1) { /* caught */
+                               t->balllink = p; /* complete trajectory */
+# if 0
+                               if (p->type == Full) {
+                                 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
+                                                 MI_SCREEN(mi), t->object->color);
+                               }
+#endif
+                               p->type = Full;
+                               DUP_OBJECT(p, t); /* accept catch */
+                               p->angle = t->angle;
+                               p->divisions = t->divisions;
+                         }
+                       }
+                 }
+               }
+               t->type = Empty; /* thrown */
+         } else if (t->action == CATCH) {
+               /* search forward for next throw from this hand */
+               for (p = t->next; t->handlink == NULL; p = p->next) {
+                 if(p->status < ACTION || p == sp->head) return;
+                 if (p->action == THROW && p->hand == t->hand) {
+                       p->type = t->type; /* pass ball */
+                       DUP_OBJECT(p, t); /* pass object */
+                       p->divisions = t->divisions;
+                       t->handlink = p;
+                 }
+               }
+         }
+         t->status = LINKEDACTION;
+       }
+  }
 }
 
-
-static void GetDefaultFromPosition(unsigned char Side, int Weight, POS* pPos)
+/* Clap when both hands are empty */
+static void
+clap(jugglestruct *sp)
 {
-    if (Weight > 4 && Weight % 2 != 0)
-        pPos->x = Side ?  -0.06f : 0.06f;
-    else if (Weight == 0 || Weight == 2)
-        pPos->x = Side ? 1.6f :  -1.6f;
-    else
-        pPos->x = Side? 0.24f :  -0.24f;
-
-    pPos->y = (Weight == 2 || Weight == 0) ? -0.25f : 0.0f;
-
-    pPos->Rot = (Weight % 2 == 0 ? -23.5f : 27.0f) * (Side ? -1.0f : 1.0f);
-
-    pPos->Elev = Weight == 1 ? -30.0f : 0.0f;
-    pPos->z = 0.0f;
+  Trajectory *t, *p;
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status == LINKEDACTION &&
+               t->action == CATCH &&
+               t->type == Empty &&
+               t->handlink != NULL &&
+               t->handlink->height == 0) { /* Completely idle hand */
+
+         for (p = t->next; p != sp->head; p = p->next) {
+               if (p->status == LINKEDACTION &&
+                       p->action == CATCH &&
+                       p->hand != t->hand) { /* Next catch other hand */
+                 if(p->type == Empty &&
+                        p->handlink != NULL &&
+                        p->handlink->height == 0) { /* Also completely idle */
+
+                       t->handlink->posn = '^'; /* Move first hand's empty throw */
+                       p->posn = '^';           /* to meet second hand's empty
+                                                                               catch */
+
+                 }
+                 break; /* Only need first catch */
+               }
+         }
+       }
+  }
 }
 
+#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
 
-static void GetDefaultToPosition(unsigned char Side, int Weight, POS* pPos)
+/* Compute single spline from x0 with velocity dx0 at time t0 to x1
+   with velocity dx1 at time t1 */
+static Spline
+makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
 {
-    if (Weight == 1)
-        pPos->x = Side ?  -1.0f : 1.0f;
-    else if (Weight % 2 == 0)
-        pPos->x = Side ? 2.8f :  -2.8f;
-    else
-        pPos->x = Side?  -3.1f : 3.1f;
-
-    pPos->y = -0.5f;
-
-    pPos->Rot = (Side ? -35.0f : 35.0f) * (Weight % 2 == 0 ? -1.0f : 1.0f);
-    
-    if (Weight < 2)
-        pPos->Elev = -30.0f;
-
-    else if (Weight < 4)
-        pPos->Elev = 360.0f - 50.0f;
-    else if (Weight < 7)
-        pPos->Elev = 720.0f - 50.0f;
-    else
-        pPos->Elev = 360.0f * GetDefaultSpins(Weight) - 50.0f;
-    pPos->z = 0.0f;
+  Spline s;
+  double a, b, c, d;
+  double x10;
+  double t10;
+
+  x10 = x1 - x0;
+  t10 = t1 - t0;
+  a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
+  b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
+  c = dx0;
+  d = x0;
+  s.a = a;
+  s.b = -3*a*t0 + b;
+  s.c = (3*a*t0 - 2*b)*t0 + c;
+  s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
+  return s;
 }
 
-
-/* Update the members of PATTERN_INFO for a given juggling pattern.  The pattern
- * can come from an ordinary siteswap (Site != NULL) or from a Juggle Saver
- * compatible pattern that contains, position and object info etc. 
- * We assume that patterns are valid and have at least one object (a site of
- * zeros is invalid).  The ones we generate randomly are safe. */
-
-static void InitPatternInfo(PATTERN_INFO* pPattern,
-    const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
+/* Compute a pair of splines.  s1 goes from x0 vith velocity dx0 at
+   time t0 to x1 at time t1.  s2 goes from x1 at time t1 to x2 with
+   velocity dx2 at time t2.  The arrival and departure velocities at
+   x1, t1 must be the same. */
+static double
+makeSplinePair(Spline *s1, Spline *s2,
+                          double x0, double dx0, int t0,
+                          double x1,             int t1,
+                          double x2, double dx2, int t2)
 {
-    /* Double up on the length of the site if it's of an odd length. 
-     * This way we can store position information: even indices are on one
-     * side and odds are on the other. */
-    int InfoLen = Len % 2 == 1 ? Len * 2 : Len;
-    int i;
-    THROW_INFO* pInfo = (THROW_INFO*) calloc(InfoLen, sizeof(THROW_INFO));
-    int Objects = 0;
-    unsigned char* pUsed;
-    
-    pPattern->MaxWeight = 0;
-    pPattern->ThrowLen = InfoLen;
-    pPattern->pThrowInfo = pInfo;
-    
-    SetHeightAndAlpha(pPattern, Site, pExtInfo, Len);
-
-    /* First pass through we assign the things we know about for sure just by
-     * looking at the throw weight at this position.  This includes TimeInAir;
-     * the throw and catch positions; and throw and catch velocities.
-     * Other information, like the total time for the throw (i.e. when the
-     * object is thrown again) relies on how the rest of the pattern is 
-     * structured and we defer this task for successive passes and just make
-     * guesses at this stage. */
-    
-    for (i = 0; i < InfoLen; i++)
-    {
-        float t1;
-        int w = pExtInfo != NULL ? pExtInfo[i % Len].Weight : Site[i % Len];
-
-        pInfo[i].TotalTime = pInfo[i].TimeInAir = w;
-        pInfo[(w + i) % Len].PrevThrow = i;
-
-        /* work out where we are throwing this object from and where it's going
-         * to land. */
-
-        if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_FROM_POS) == 0)
-            GetDefaultFromPosition(i % 2, w, &pInfo[i].FromPos);
-        else
-            pInfo[i].FromPos = pExtInfo[i % Len].FromPos;
-
-        if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_TO_POS) == 0)
-            GetDefaultToPosition(i % 2, w, &pInfo[i].ToPos);
-        else
-            pInfo[i].ToPos = pExtInfo[i % Len].ToPos;
-
-        /* calculate the velocity the object is moving at the start and end
-         * points -- this information is used to interpolate the hand position
-         * and to determine how the object is moved while it's carried to the 
-         * next throw position.
-         *
-         * The throw motion is governed by a parabola of the form:
-         *   y(t) = a * t ^ 2 + b * t + c
-         * Assuming at the start of the throw y(0) = y0; when it's caught
-         * y(t1) = y1; and the accelation is -2.0 * alpha the equation can be
-         * rewritten as:
-         *   y(t) = -alpha * t ^ 2 + (alpha * t1 + (y1 - y0) / t1) * t + y0
-         * making the velocity:
-         *   y'(t) = -2.0 * alpha * t + (alpha * t1 + (y1 - y0) / t1)
-         * To get the y component of velocity first we determine t1, which is
-         * the throw weight minus the time spent carrying the object.  Then
-         * perform the relevant substitutions into the above.
-         * (note: y'(t) = y'(0) - 2.0 * alpha * t)
-         * 
-         * The velocity in the x direction is constant and can be simply
-         * obtained from:
-         *   x' = (x1 - x0) / t1
-         * where x0 and x1 are the start and end x-positions respectively.
-         */
-
-        t1 = w - CARRY_TIME;
-
-        pInfo[i].FromVelocity.y = pPattern->Alpha * t1 + 
-            (pInfo[i].ToPos.y - pInfo[i].FromPos.y) / t1;
-        pInfo[i].ToVelocity.y = 
-            pInfo[i].FromVelocity.y - 2.0f * pPattern->Alpha * t1;
-        pInfo[i].FromVelocity.x = pInfo[i].ToVelocity.x = 
-            (pInfo[i].ToPos.x - pInfo[i].FromPos.x) / t1;
-        pInfo[i].FromVelocity.z = pInfo[i].ToVelocity.z = 
-            (pInfo[i].ToPos.z - pInfo[i].FromPos.z) / t1;
-        pInfo[i].FromVelocity.Rot = pInfo[i].ToVelocity.Rot =
-            (pInfo[i].ToPos.Rot - pInfo[i].FromPos.Rot) / t1;
-        pInfo[i].FromVelocity.Elev = pInfo[i].ToVelocity.Elev =
-            (pInfo[i].ToPos.Elev - pInfo[i].FromPos.Elev) / t1;
-
-
-        if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SNATCH) != 0)
-        {
-            pInfo[i].ToVelocity.x = pExtInfo[i % Len].SnatchX;
-            pInfo[i].ToVelocity.y = pExtInfo[i % Len].SnatchY;
-        }
-
-        if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SPINS) != 0)
-        {
-            pInfo[i].ToPos.Elev = 360.0f * pExtInfo[i % Len].Spins +
-                NormaliseAngle(pInfo[i].ToPos.Elev);
-        }
-
-        Objects += w;
-        if (w > pPattern->MaxWeight)
-            pPattern->MaxWeight = w;
-    }
+  double x10, x21, t21, t10, t20, dx1;
+  x10 = x1 - x0;
+  x21 = x2 - x1;
+  t21 = t2 - t1;
+  t10 = t1 - t0;
+  t20 = t2 - t0;
+  dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
+                - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
+       (2*t10*t21*t20);
+  *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
+  *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
+  return dx1;
+}
 
-    Objects /= InfoLen;
+/* Compute a Ballistic path in a pair of degenerate splines.  sx goes
+   from x at time t at constant velocity dx.  sy goes from y at time t
+   with velocity dy and constant acceleration g. */
+static void
+makeParabola(Trajectory *n,
+                        double x, double dx, double y, double dy, double g)
+{
+  double t = (double)n->start;
+  n->xp.a = 0;
+  n->xp.b = 0;
+  n->xp.c = dx;
+  n->xp.d = -dx*t + x;
+  n->yp.a = 0;
+  n->yp.b = g/2;
+  n->yp.c = -g*t + dy;
+  n->yp.d = g/2*t*t - dy*t + y;
+}
 
-    /* Now we go through again and work out exactly how long it is before the
-     * object is thrown again (ie. the TotalTime) typically this is the same
-     * as the time in air, however when we have a throw weight of '2' it's
-     * treated as a hold and we increase the total time accordingly. */
 
-    for (i = 0; i < InfoLen; i++)
-    {
-        if (pInfo[i].TimeInAir != 2)
-        {
-            int Next = pInfo[i].TimeInAir + i;
-            while (pInfo[Next % InfoLen].TimeInAir == 2)
-            {
-                Next += 2;
-                pInfo[i].TotalTime += 2;
-            }
 
-            /* patch up the Prev index.  We don't bother to see if this
-             * is different from before since it's always safe to reassign it */
-            pInfo[Next % InfoLen].PrevThrow = i;
-        }
-    }
 
-    /* then we work our way through again figuring out where the hand goes to
-     * catch something as soon as it has thrown the current object. */
+#define SX 25 /* Shoulder Width */
 
-    for (i = 0; i < InfoLen; i++)
-    {
-        if (pInfo[i].TimeInAir != 0 && pInfo[i].TimeInAir != 2)
-        {
-            /* what we're trying to calculate is how long the hand that threw
-             * the current object has to wait before it throws another.
-             * Typically this is two beats later.  However '0' in the site swap
-             * represents a gap in a catch, and '2' represents a hold.  We skip
-             * over these until we reach the point where a ball is actually
-             * thrown. */
-            int Wait = 2;
-            while (pInfo[(i + Wait) % InfoLen].TimeInAir == 2 || 
-                pInfo[(i + Wait) % InfoLen].TimeInAir == 0)
-            {
-                Wait += 2;
-            }
-            pInfo[i].NextForHand = Wait;
-        }
-        else
-        {
-            /* Be careful to ensure the current weight isn't one we're trying
-             * to step over; otherwise we could potentially end up in an 
-             * infinite loop.  The value we assign may end up being used
-             * in patterns with infinite gaps (e.g. 60) or infinite holds
-             * (e.g. 62) in both cases, setting a wait of 2 ensures things
-             * are well behaved. */
-            pInfo[i].NextForHand = 2;
-        }
-    }
+/* Convert hand position symbols into actual time/space coordinates */
+static void
+positions(jugglestruct *sp)
+{
+  Trajectory *t;
+  unsigned long now = sp->time; /* Make sure we're not lost in the past */
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status >= PTHRATCH) {
+         now = t->start;
+       } else if (t->status == ACTION || t->status == LINKEDACTION) {
+         /* Allow ACTIONs to be annotated, but we won't mark them ready
+                for the next stage */
+
+         double xo = 0, yo;
+         double sx = SX;
+         double pose = SX/2;
+
+         /* time */
+         if (t->action == CATCH) { /* Throw-to-catch */
+               if (t->type == Empty) {
+                 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
+               } else {     /* successful catch */
+                 now += (int)(THROW_CATCH_INTERVAL);
+               }
+         } else { /* Catch-to-throw */
+               if(t->object != NULL) {
+                 now += (int) (CATCH_THROW_INTERVAL *
+                                               ObjectDefs[t->object->type].weight);
+               } else {
+                 now += (int) (CATCH_THROW_INTERVAL);
+               }
+         }
+
+         if(t->start == 0)
+               t->start = now;
+         else /* Concatenated performances may need clock resync */
+               now = t->start;
+
+         t->cx = 0;
+
+         /* space */
+         yo = 90;
+
+         /* Add room for the handle */
+         if(t->action == CATCH && t->object != NULL)
+               yo -= ObjectDefs[t->object->type].handle;
+
+         switch (t->posn) {
+         case '-': xo = sx - pose; break;
+         case '_':
+         case 'k':
+         case '+': xo = sx + pose; break;
+         case '~':
+         case '=': xo = - sx - pose; yo += pose; break;
+         case '^': xo = 0; yo += pose*2; break; /* clap */
+         default:
+               (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
+               break;
+         }
+
+#ifdef _2DSpinsDontWorkIn3D
+         t->angle = (((t->hand == LEFT) ^
+                                  (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
+                                       -1 : 1) * M_PI/2;
+#else
+         t->angle = -M_PI/2;
+#endif
 
-    /* Now work out the starting positions for the objects.  To do this we
-     * unweave the initial throws so we can pick out the individual threads. */
+         t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
+         t->y = yo;
 
-    pUsed = (unsigned char*) 
-        malloc(sizeof(unsigned char) * pPattern->MaxWeight);
-    pPattern->Objects = Objects;
-    pPattern->pObjectInfo = (OBJECT_POSITION*) calloc(
-        Objects, sizeof(OBJECT_POSITION));
+         /* Only mark complete if it was already linked */
+         if(t->status == LINKEDACTION) {
+               t->status = PTHRATCH;
+         }
+       }
+  }
+}
 
-    for (i = 0; i < pPattern->MaxWeight; i++)
-        pUsed[i] = 0;
 
-    for (i = 0; i < pPattern->MaxWeight; i++)
-    {
-        int w = pInfo[i % InfoLen].TimeInAir;
-        if (pUsed[i] == 0 &&  w != 0)
-        {
-            Objects--;
-            pPattern->pObjectInfo[Objects].TimeOffset = i;
-            pPattern->pObjectInfo[Objects].ThrowIndex = i % InfoLen;
-            pPattern->pObjectInfo[Objects].TotalTwist = 0.0f;
+/* Private physics functions */
 
-            if (pExtInfo != NULL && 
-                pExtInfo[i % Len].ObjectType != OBJECT_DEFAULT)
-            {
-                pPattern->pObjectInfo[Objects].ObjectType =
-                    pExtInfo[i % Len].ObjectType;
-            }
-            else
-            {
-                pPattern->pObjectInfo[Objects].ObjectType = (1 + random() % 3);
-            }
-        }
+/* Compute the spin-rate for a trajectory.  Different types of throw
+   (eg, regular thows, bounces, kicks, etc) have different spin
+   requirements.
 
-        if (w + i < pPattern->MaxWeight)
-            pUsed[w + i] = 1;
-        
-    }
+   type = type of object
+   h = trajectory of throwing hand (throws), or next throwing hand (catches)
+   old = earlier spin to consider
+   dt = time span of this trajectory
+   height = height of ball throw or 0 if based on old spin
+   turns = full club turns required during this operation
+   togo = partial club turns required to match hands
+*/
+static double
+spinrate(ObjType type, Trajectory *h, double old, double dt,
+                int height, int turns, double togo)
+{
+#ifdef _2DSpinsDontWorkIn3D
+  const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
+#else
+  const int dir = 1;
+#endif
 
-    pPattern->LeftHand.TimeOffset = pPattern->LeftHand.ThrowIndex = 0;
-    pPattern->RightHand.TimeOffset = pPattern->RightHand.ThrowIndex = 1;
-    
-    free(pUsed);
+  if(ObjectDefs[type].handle != 0) { /* Clubs */
+       return (dir * turns * 2 * M_PI + togo) / dt;
+  } else if(height == 0) { /* Balls already spinning */
+       return old/2;
+  } else { /* Balls */
+       return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
+  }
 }
 
 
-static void ReleasePatternInfo(PATTERN_INFO* pPattern)
+/* compute the angle at the end of a spinning trajectory */
+static double
+end_spin(Trajectory *t)
 {
-    free(pPattern->pObjectInfo);
-    free(pPattern->pThrowInfo);
+  return t->angle + t->spin * (t->finish - t->start);
 }
 
-
-/*****************************************************************************
- *
- * Sites
- *
- ****************************************************************************/
-
-/* Generate a random site swap.  We assume that MaxWeight >= ObjCount and
- * Len >= MaxWeight. */
-static int* Generate(int Len, int MaxWeight, int ObjCount)
+/* Sets the initial angle of the catch following hand movement t to
+   the final angle of the throw n.  Also sets the angle of the
+   subsequent throw to the same angle plus half a turn. */
+static void
+match_spins_on_catch(Trajectory *t, Trajectory *n)
 {
-    int* Weight = (int*) calloc(Len, sizeof(int));
-    int* Used = (int*) calloc(Len, sizeof(int));
-    int* Options = (int*) calloc(MaxWeight + 1, sizeof(int));
-    int nOpts;
-    int i, j;
-
-    for (i = 0; i < Len; i++)
-        Weight[i] = Used[i] = -1;
-    
-    /* Pick out a unique the starting position for each object.  -2 is put in
-     * the Used array to signify this is a starting position. */
-
-    while (ObjCount > 0)
-    {
-        nOpts = 0;
-        for (j = 0; j < MaxWeight; j++)
-        {
-            if (Used[j] == -1)
-                Options[nOpts++] = j;
-        }
-
-        Used[Options[random() % nOpts]] = -2;
-        ObjCount--;
-    }
-    
-    /* Now work our way through the pattern moving throws into an available
-     * landing positions. */
-    for (i = 0; i < Len; i++)
-    {
-        if (Used[i] == -1)
-        {
-            /* patch up holes in the pattern to zeros */
-            Used[i] = 1;
-            Weight[i] = 0;
-        }
-        else
-        {
-            /* Work out the possible places where a throw can land and pick a 
-             * weight at random. */
-            int w;
-            nOpts = 0;
-
-            for (j = 0 ; j <= MaxWeight; j++)
-            {
-                if (Used[(i + j) % Len] == -1)
-                    Options[nOpts++] = j;
-            }
-            
-            w = Options[random() % nOpts];
-            Weight[i] = w;
-            
-            /* For starting throws make position available for a throw to land.
-             * Because Len >= MaxWeight these positions will only be filled when
-             * a throw wraps around the end of the site swap and therefore we
-             * can guarantee the all the object threads will be tied up. */
-            if (Used[i] == -2)
-                Used[i] = -1;
-            
-            Used[(i + w) % Len] = 1;
-        }
-    }
-
-    free(Options);
-    free(Used);
-    return Weight;
+  if(ObjectDefs[t->balllink->object->type].handle == 0) {
+       t->balllink->angle = end_spin(n);
+       if(t->balllink->handlink != NULL) {
+#ifdef _2DSpinsDontWorkIn3D
+         t->balllink->handlink->angle = t->balllink->angle + M_PI;
+#else
+         t->balllink->handlink->angle = t->balllink->angle;
+#endif
+       }
+  }
 }
 
-
-/* Routines to parse the Juggle Saver patterns.  These routines are a bit yucky
- * and make the big assumption that the patterns are well formed.  This is fine
- * as it stands because only 'good' ones are used but if the code is ever
- * extended to read arbitrary patterns (say from a file) then these routines
- * need to be beefed up. */
-
-/* The position text looks something like (x,y,z[,rot[,elev]])
- * where the stuff in square brackets is optional */
-
-static unsigned char ParsePositionText(const char** ppch, POS* pPos)
+static double
+find_bounce(jugglestruct *sp,
+                       double yo, double yf, double yc, double tc, double cor)
 {
-    const char* pch = *ppch;
-    unsigned char OK;
-    char szTemp[32];
-    char* pOut;
-    float* Nums[4];
-    int i;
-    
-    Nums[0] = &pPos->x;
-    Nums[1] = &pPos->y;
-    Nums[2] = &pPos->Rot;
-    Nums[3] = &pPos->Elev;
-
-
-    while (*pch == ' ')
-        pch++;
-    
-    OK = *pch == '(';
-    
-    if (OK)
-        pch++;
-
-    for (i = 0; OK && i < 4; i++)
-    {
-        pOut = szTemp;
-        while (*pch == ' ')
-            pch++;
-        while (*pch != ',' && *pch != '\0' && *pch != ')' && *pch != ' ')
-            *pOut++ = *pch++;
-        *pOut = '\0';
-
-        if (szTemp[0] != '\0')
-            *Nums[i] = (float) atof(szTemp);
-
-        while (*pch == ' ')
-            pch++;
-
-        if (i < 3)
-        {
-            if (*pch == ',')
-                pch++;
-            else if (*pch == ')')
-                break;
-            else
-                OK = 0;
-        }
-    }
-
-    if (OK)
-    {
-        while (*pch == ' ')
-            pch++;        
-        if (*pch == ')')
-            pch++;
-        else
-            OK = 0;
-    }
-
-    *ppch = pch;
-
-    return OK;
+  double tb, i, dy = 0;
+  const double e = 1; /* permissible error in yc */
+
+  /*
+       tb = time to bounce
+       yt = height at catch time after one bounce
+       one or three roots according to timing
+       find one by interval bisection
+  */
+  tb = tc;
+  for(i = tc / 2; i > 0.0001; i/=2){
+       double dt, yt;
+       if(tb == 0){
+         (void) fprintf(stderr, "juggle: bounce div by zero!\n");
+         break;
+       }
+       dy = (yf - yo)/tb + sp->Gr/2*tb;
+       dt = tc - tb;
+       yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
+       if(yt < yc + e){
+         tb-=i;
+       }else if(yt > yc - e){
+         tb+=i;
+       }else{
+         break;
+       }
+  }
+  if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
+       tb = -1;
+  }
+  return tb;
 }
 
-
-static EXT_SITE_INFO* ParsePattern(const char* Site, int* pLen)
+static Trajectory*
+new_predictor(const Trajectory *t, int start, int finish, double angle)
 {
-    const char* pch = Site;
-    int Len = 0;
-    EXT_SITE_INFO* pInfo = NULL;
-    unsigned char OK = 1;
-
-    while (OK && *pch != 0)
-    {
-        EXT_SITE_INFO Info;
-        Info.Flags = 0;
+  Trajectory *n;
+  ADD_ELEMENT(Trajectory, n, t->prev);
+  if(n == NULL){
+       return NULL;
+  }
+  DUP_OBJECT(n, t);
+  n->divisions = t->divisions;
+  n->type = Ball;
+  n->status = PREDICTOR;
+
+  n->start = start;
+  n->finish = finish;
+  n->angle = angle;
+  return n;
+}
 
-        while (*pch == ' ') pch++;
+/* Turn abstract timings into physically appropriate object trajectories. */
+static Bool
+projectile(jugglestruct *sp)
+{
+  Trajectory *t;
+  const int yf = 0; /* Floor height */
+
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if (t->status != PTHRATCH || t->action != THROW) {
+         continue;
+       } else if (t->balllink == NULL) { /* Zero Throw */
+         t->status = BPREDICTOR;
+       } else if (t->balllink->handlink == NULL) { /* Incomplete */
+         return True;
+       } else if(t->balllink == t->handlink) {
+         /* '2' height - hold on to ball.  Don't need to consider
+                flourishes, 'hands' will do that automatically anyway */
+
+         t->type = Full;
+         /* Zero spin to avoid wrist injuries */
+         t->spin = 0;
+         match_spins_on_catch(t, t);
+         t->dx = t->dy = 0;
+         t->status = BPREDICTOR;
+         continue;
+       } else {
+         if (t->posn == '_') { /* Bounce once */
+
+               const int tb = t->start +
+                 find_bounce(sp, t->y, (double) yf, t->balllink->y,
+                                         (double) (t->balllink->start - t->start),
+                                         ObjectDefs[t->object->type].cor);
+
+               if(tb < t->start) { /* bounce too hard */
+                 t->posn = '+'; /* Use regular throw */
+               } else {
+                 Trajectory *n; /* First (throw) trajectory. */
+                 double dt; /* Time span of a trajectory */
+                 double dy; /* Distance span of a follow-on trajectory.
+                                               First trajectory uses t->dy */
+                 /* dx is constant across both trajectories */
+                 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
+
+                 { /* ball follows parabola down */
+                       n = new_predictor(t, t->start, tb, t->angle);
+                       if(n == NULL) return False;
+                       dt = n->finish - n->start;
+                       /* Ball rate 4, no flight or matching club turns */
+                       n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
+                       t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
+                       makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
+                 }
+
+                 { /* ball follows parabola up */
+                       Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
+                                                                                 end_spin(n));
+                       if(m == NULL) return False;
+                       dt = m->finish - m->start;
+                       /* Use previous ball rate, no flight club turns */
+                       m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
+                                                          t->balllink->angle - m->angle);
+                       match_spins_on_catch(t, m);
+                       dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
+                       makeParabola(m, t->balllink->x - t->dx * dt,
+                                                t->dx, (double) yf, dy, sp->Gr);
+                 }
+
+                 t->status = BPREDICTOR;
+                 continue;
+               }
+         } else if (t->posn == 'k') { /* Drop & Kick */
+               Trajectory *n; /* First (drop) trajectory. */
+               Trajectory *o; /* Second (rest) trajectory */
+               Trajectory *m; /* Third (kick) trajectory */
+               const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
+               const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
+               double dt, dy;
+
+               { /* Fall to ground */
+                 n = new_predictor(t, t->start, td, t->angle);
+                 if(n == NULL) return False;
+                 dt = n->finish - n->start;
+                 /* Ball spin rate 4, no flight club turns */
+                 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
+                                                        t->balllink->angle - n->angle);
+                 t->dx = (t->balllink->x - t->x) / dt;
+                 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
+                 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
+               }
+
+               { /* Rest on ground */
+                 o = new_predictor(t, n->finish, tk, end_spin(n));
+                 if(o == NULL) return False;
+                 o->spin = 0;
+                 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
+               }
+
+               /* Kick up */
+               {
+                 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
+                 if(m == NULL) return False;
+                 dt = m->finish - m->start;
+                 /* Match receiving hand, ball rate 4, one flight club turn */
+                 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
+                                                        4, 1, t->balllink->angle - m->angle);
+                 match_spins_on_catch(t, m);
+                 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
+                 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
+               }
+
+               t->status = BPREDICTOR;
+               continue;
+         }
+
+         /* Regular flight, no bounce */
+         { /* ball follows parabola */
+               double dt;
+               Trajectory *n = new_predictor(t, t->start,
+                                                                         t->balllink->start, t->angle);
+               if(n == NULL) return False;
+               dt = t->balllink->start - t->start;
+               /* Regular spin */
+               n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
+                                                  t->balllink->angle - n->angle);
+               match_spins_on_catch(t, n);
+               t->dx = (t->balllink->x - t->x) / dt;
+               t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
+               makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
+         }
+
+         t->status = BPREDICTOR;
+       }
+  }
+  return True;
+}
 
-        OK = *pch != '\0';
+/* Turn abstract hand motions into cubic splines. */
+static void
+hands(jugglestruct *sp)
+{
+  Trajectory *t, *u, *v;
 
-        if (OK)
-            Info.Weight = *pch >= 'A' ? *pch + 10 - 'A' : *pch - '0';
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       /* no throw => no velocity */
+       if (t->status != BPREDICTOR) {
+         continue;
+       }
 
-        /* parse object type */
-        if (OK)
-        {
-            pch++;
-            while (*pch == ' ') pch++;
+       u = t->handlink;
+       if (u == NULL) { /* no next catch */
+         continue;
+       }
+       v = u->handlink;
+       if (v == NULL) { /* no next throw */
+         continue;
+       }
 
-            if (*pch == 'b' || *pch == 'B')
-            {
-                Info.ObjectType = OBJECT_BALL;
-                pch++;
-            }
-            else if (*pch == 'c' || *pch == 'C')
-            {
-                Info.ObjectType = OBJECT_CLUB;
-                pch++;
-            }
-            else if (*pch == 'r' || *pch == 'R')
-            {
-                Info.ObjectType = OBJECT_RING;
-                pch++;
-            }
-            else if (*pch == 'd' || *pch == 'D')
-            {
-                Info.ObjectType = OBJECT_DEFAULT;
-                pch++;
-            }
-            else
-            {
-                Info.ObjectType = OBJECT_DEFAULT;
-            }
-        }
+       /* double spline takes hand from throw, thru catch, to
+          next throw */
 
-        /* Parse from position */
-        if (OK)
-        {
-            while (*pch == ' ') pch++;
-            if (*pch == '@')
-            {
-                pch++;
-                GetDefaultFromPosition(Len % 2, Info.Weight, &Info.FromPos);
-                Info.Flags |= HAS_FROM_POS;
-                OK = ParsePositionText(&pch, &Info.FromPos);
-            }
-        }
+       t->finish = u->start;
+       t->status = PREDICTOR;
 
-        /* Parse to position */
-        if (OK)
-        {
-            while (*pch == ' ') pch++;
-            if (*pch == '>')
-            {
-                pch++;
-                GetDefaultToPosition(Len % 2, Info.Weight, &Info.ToPos);
-                Info.Flags |= HAS_TO_POS;
-                OK = ParsePositionText(&pch, &Info.ToPos);
-            }
-        }
+       u->finish = v->start;
+       u->status = PREDICTOR;
 
-        /* Parse snatch */
-        if (OK)
-        {
-            while (*pch == ' ') pch++;
-            if (*pch == '/')
-            {
-                POS Snatch;
-                pch++;
-                Info.Flags |= HAS_SNATCH;
-                OK = ParsePositionText(&pch, &Snatch);
-                Info.SnatchX = Snatch.x;
-                Info.SnatchY = Snatch.y;
-            }
-        }
 
-        /* Parse Spins */
-        if (OK)
-        {
-            while (*pch == ' ') pch++;
-            if (*pch == '*')
-            {
-                pch++;
-                OK = 0;
-                Info.Spins = 0;
-                while (*pch >= '0' && *pch <= '9')
-                {
-                    OK = 1;
-                    Info.Spins = Info.Spins * 10 + *pch - '0';
-                    pch++;
-                }
-            }
-            else
-                Info.Spins = GetDefaultSpins(Info.Weight);
+       /* FIXME: These adjustments leave a small glitch when alternating
+          balls and clubs.  Just hope no-one notices.  :-) */
 
-            Info.Flags |= HAS_SPINS;
-        }
+       /* make sure empty hand spin matches the thrown object in case it
+          had a handle */
 
-        if (OK)
-        {
-            if (pInfo == NULL)
-                pInfo = (EXT_SITE_INFO*) malloc(sizeof(EXT_SITE_INFO));
-            else
-                pInfo = (EXT_SITE_INFO*) realloc(pInfo, (Len + 1) * sizeof(EXT_SITE_INFO));
+       t->spin = ((t->hand == LEFT)? -1 : 1 ) *
+         fabs((u->angle - t->angle)/(u->start - t->start));
 
-            pInfo[Len] = Info;
-            Len++;
-        }
-    }
-
-    if (!OK && pInfo != NULL)
-    {
-        free(pInfo);
-        pInfo = NULL;
-    }
+       u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
+         fabs((v->angle - u->angle)/(v->start - u->start));
 
-    *pLen = Len;
+       (void) makeSplinePair(&t->xp, &u->xp,
+                                                 t->x, t->dx, t->start,
+                                                 u->x, u->start,
+                                                 v->x, v->dx, v->start);
+       (void) makeSplinePair(&t->yp, &u->yp,
+                                                 t->y, t->dy, t->start,
+                                                 u->y, u->start,
+                                                 v->y, v->dy, v->start);
 
-    return pInfo;
+       t->status = PREDICTOR;
+  }
 }
 
-
-/*****************************************************************************
- *
- *  Juggle Saver Patterns
- *
- *****************************************************************************
- *
- * This is a selection of some of the more interesting patterns from taken
- * from the Juggle Saver sites.txt file.  I've only used patterns that I
- * originally created.
- */
-
-static const char* PatternText[] =
-{
-    "9b@(-2.5,0,-70,40)>(2.5,0,70)*2 1b@(1,0,10)>(-1,0,-10)",
-    
-    "3B@(1,-0.4)>(2,4.2)/(-2,1)3B@(-1.8,4.4)>(-2.1,0)",
-    
-    "7c@(-2,0,-20)>(1.2,0,-5)7c@(2,0,20)>(-1.2,0,5)",
-    
-    "3b@(-0.5,0)>(1.5,0) 3b@(0.5,0)>(-1.5,0) 3r@(-2.5,3,-90,80)>(2,1,90,30)"
-    "3b@(0.5,0)>(-1.5,0) 3b@(-0.5,0)>(1.5,0) 3r@(2.5,3,90,80)>(-2,1,-90,30)",
-    
-    "5c@(2,1.9,10)>(-1,1,10)5c@(2,1.8,10)>(-0.5,1.6,10)/(5,-1)"
-    "5c@(1.6,0.2,10)>(0,-1,10)/(9,-2)5c@(-2,1.9,-10)>(1,1,-10)"
-    "5c@(-2,1.8,-10)>(0.5,1.6,-10)/(-5,-1)5@(-1.6,0.2,-10)>(0,-1,-10)/(-9,-2)",
-    
-    "3c@(-1.5,0,0)>(-1.5,1,0)3c@(1.5,-0.2,0)>(1.5,-0.1,0)3c@(0,-0.5,0)>(0,1,0)"
-    "3@(-1.5,2,0)>(-1.5,-1,0)3@(1.5,0,0)>(1.5,1,0)3@(0,0,0)>(0,-0.5,0)",
-    
-    "9c@(-2.5,0,-70,40)>(2.5,0,70)*2 1c@(1,0,10)>(-1,0,-10)*0",
-    
-    "3c@(2,0.5,60,0)>(1.5,4,60,80)/(-6,-12)"
-    "3c@(-2,0.5,-60,0)>(-1.5,4,-60,80)/(6,-12)",
-    
-    "3c@(-0.2,0)>(1,0)3c@(0.2,0)>(-1,0)3c@(-2.5,2,-85,30)>(2.5,2,85,40)*2 "
-    "3@(0.2,0)>(-1,0) 3@(-0.2,0)>(1,0) 3@(2.5,2,85,30)>(-2.5,2,-85,40)*2",
-    
-    "3c@(-0.5,-0.5,20,-30)>(2.6,4.3,60,60)/(0,1)*1 "
-    "3c@(1.6,5.6,60,80)>(-2.6,0,-80)*0",
-    
-    "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)"
-    "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)"
-    "5c@(-3,3.5,-65,80)>(3,2.5,65) 5c@(0.3,0,-10)>(-1.2,0,-10)"
-    "5@(-0.3,0,10)>(1.2,0,10) 5@(0.3,0,-10)>(-1.2,0,-10)"
-    "5@(-0.3,0,10)>(1.2,0,10)5@(3,3.5,65,80)>(-3,2.5,-65)"
-};
-
-
-/*****************************************************************************
- *
- * Rendering
- *
- *****************************************************************************/
-
-static const float FOV = 70.0f;
-static const float BodyCol[] = {0.6f, 0.6f, 0.45f, 1.0f};
-static const float HandleCol[] = {0.45f, 0.45f, 0.45f, 1.0f};
-static const float LightPos[] = {0.0f, 200.0f, 400.0f, 1.0f};
-static const float LightDiff[] = {1.0f, 1.0f, 1.0f, 0.0f};
-static const float LightAmb[] = {0.02f, 0.02f, 0.02f, 0.0f};
-static const float ShoulderPos[3] = {0.95f, 2.1f, 1.7f};
-static const float DiffCol[] = {1.0f, 0.0f, 0.0f, 1.0f};
-static const float SpecCol[] = {1.0f, 1.0f, 1.0f, 1.0f};
-
-static const float BallRad = 0.34f;
-static const float UArmLen = 1.9f;
-static const float LArmLen = 2.3f;
-
-#define DL_BALL 0
-#define DL_CLUB 1
-#define DL_RING 2
-#define DL_TORSO 3
-#define DL_FOREARM 4
-#define DL_UPPERARM 5
-
-static const float AltCols[][4] =
-{
-    {0.0f, 0.7f, 0.0f, 1.0f},
-    {0.0f, 0.0f, 0.9f, 1.0f},
-    {0.0f, 0.9f, 0.9f, 1.0f},
-    {0.45f, 0.0f, 0.9f, 1.0f},
-    {0.9f, 0.45f, 0.0f, 1.0f},
-    {0.0f, 0.45f, 0.9f, 1.0f},
-    {0.9f, 0.0f, 0.9f, 1.0f},
-    {0.9f, 0.9f, 0.0f, 1.0f},
-    {0.9f, 0.0f, 0.45f, 1.0f},
-    {0.45f, 0.15f, 0.6f, 1.0f}, 
-    {0.9f, 0.0f, 0.0f, 1.0f},
-    {0.0f, 0.9f, 0.45f, 1.0f},
-};
-
-static const float Cols[][4] =
+/* Given target x, y find_elbow puts hand at target if possible,
+ * otherwise makes hand point to the target */
+static void
+find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
+                  int z)
 {
-    {0.9f, 0.0f, 0.0f, 1.0f},  /*  0 */
-    {0.0f, 0.7f, 0.0f, 1.0f},  /*  1 */
-    {0.0f, 0.0f, 0.9f, 1.0f},  /*  2 */
-    {0.0f, 0.9f, 0.9f, 1.0f},  /*  3 */
-    {0.9f, 0.0f, 0.9f, 1.0f},  /*  4 */
-    {0.9f, 0.9f, 0.0f, 1.0f},  /*  5 */
-    {0.9f, 0.45f, 0.0f, 1.0f}, /*  6 */
-    {0.9f, 0.0f, 0.45f, 1.0f}, /*  7 */
-    {0.45f, 0.9f, 0.0f, 1.0f}, /*  8 */
-    {0.0f, 0.9f, 0.45f, 1.0f}, /*  9 */
-    {0.45f, 0.0f, 0.9f, 1.0f}, /* 10 */
-    {0.0f, 0.45f, 0.9f, 1.0f}, /* 11 */
-};
-
-static int InitGLDisplayLists(void);
+  double r, h2, t;
+  double x = p->x - s->x;
+  double y = p->y - s->y;
+  h2 = x*x + y*y + z*z;
+  if (h2 > 4 * armlength * armlength) {
+       t = armlength/sqrt(h2);
+       e->x = t*x + s->x;
+       e->y = t*y + s->y;
+       h->x = 2 * t * x + s->x;
+       h->y = 2 * t * y + s->y;
+  } else {
+       r = sqrt((double)(x*x + z*z));
+       t = sqrt(4 * armlength * armlength / h2 - 1);
+       e->x = x*(1 + y*t/r)/2 + s->x;
+       e->y = (y - r*t)/2 + s->y;
+       h->x = x + s->x;
+       h->y = y + s->y;
+  }
+}
 
 
-static void InitGLSettings(RENDER_STATE* pState, int WireFrame)
+/* NOTE: returned x, y adjusted for arm reach */
+static void
+reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
 {
-    memset(pState, 0, sizeof(RENDER_STATE));
-    
-    pState->trackball = gltrackball_init ();
-
-    if (WireFrame)
-        glPolygonMode(GL_FRONT, GL_LINE);
-    
-    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-
-    glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
-    glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiff);
-    glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);
-    
-    glEnable(GL_SMOOTH);
-    glEnable(GL_LIGHTING);
-    glEnable(GL_LIGHT0);
-
-    glDepthFunc(GL_LESS);
-    glEnable(GL_DEPTH_TEST);
-
-    glCullFace(GL_BACK);
-    glEnable(GL_CULL_FACE);
-    
-    pState->DLStart = InitGLDisplayLists();
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  DXPoint h, e;
+  find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
+  *p = sp->arm[1][side][HAND] = h;
+  sp->arm[1][side][ELBOW] = e;
 }
 
-
-static void SetCamera(RENDER_STATE* pState)
+#if DEBUG
+/* dumps a human-readable rendition of the current state of the juggle
+   pipeline to stderr for debugging */
+static void
+dump(jugglestruct *sp)
 {
-    /* Try to work out a sensible place to put the camera so that more or less
-     * the whole juggling pattern fits into the screen. We assume that the
-        * pattern is height limited (i.e. if we get the height right then the width
-        * will be OK).  This is a pretty good assumption given that the screen
-        * tends to wider than high, and that a juggling pattern is normally much
-        * higher than wide.
-     *
-     * If I could draw a diagram here then it would be much easier to
-     * understand but my ASCII-art skills just aren't up to it.  
-     *
-     * Basically we estimate a bounding volume for the juggler and objects 
-     * throughout the pattern.  We don't fully account for the fact that the
-     * juggler moves across the stage in an epicyclic-like motion and instead
-     * use the near and far planes in x-y (with z = +/- w).  We also
-     * assume that the scene is centred at x=0, this reduces our task to finding
-     * a bounding rectangle.  Finally we need to make an estimate of the
-     * height - for this we work out the max height of a standard throw or max
-     * weight from the pattern; we then do a bit of adjustment to account for
-     * a throw occurring at non-zero y values.
-     *
-     * Next we work out the best way to fit this rectangle into the perspective
-     * transform.  Based on the angle of elevation (+ve angle looks down) and
-     * the FOV we can work out whether it's the near or far corners that are
-     * the extreme points.  And then trace back from them to find the eye
-     * point.
-     *
-     */
-     
-    float ElevRad = pState->CameraElev * PI / 180.0f;
-    float w = 3.0f;
-    float cy, cz;
-    float ey, ez;
-    float d;
-    float H = 0.0f;
-    int i;
-    float a;
-    
-    float tz, ty, ta;
-    float bz, by, ba;
-    const PATTERN_INFO* pPattern = pState->pPattern;
-
-    glMatrixMode(GL_PROJECTION);
-    glLoadIdentity();
-        
-    for (i = 0; i < pPattern->ThrowLen; i++)
-        H = max(H, pPattern->pThrowInfo[i].FromPos.y);
-        
-    H += pPattern->Height;
-    
-    ElevRad = pState->CameraElev * PI / 180.0f;
-    
-    /* ta is the angle from a point on the top of the bounding area to the eye
-     * similarly ba is the angle from a point on the bottom. */
-    ta = (pState->CameraElev  - (FOV - 10.0f) / 2.0f) * PI / 180.0f;
-    ba = (pState->CameraElev  + (FOV - 10.0f) / 2.0f) * PI / 180.0f;
-
-    /* tz and bz hold the z location of the top and bottom extreme points.
-     * For the top, if the angle to the eye location is positive then the
-     * extreme point is with far z corner (the camera looks in -ve z).
-     * The logic is reserved for the bottom. */
-    tz = ta >= 0.0f ? -w : w;
-    bz = ba >= 0.0f ? w : -w;
-    
-    ty = H;
-    by = -1.0f;
-    
-    /* Solve of the eye location by using a bit of geometry.
-     * We know the eye lies on intersection of two lines.  One comes from the
-     * top and other from the bottom. Giving two equations:
-     *   ez = tz + a * cos(ta) = bz + b * cos(ba)
-     *   ey = ty + a * sin(ta) = by + b * sin(ba)
-     * We don't bother to solve for b and use Crammer's rule to get
-     *         | bz-tz  -cos(ba) |
-     *         | by-ty  -sin(ba) |     
-     *   a =  ----------------------
-     *        | cos(ta)   -cos(ba) |
-     *        | sin(ta)   -sin(ba) |
-     */
-    d = cosf(ba) * sinf(ta) - cosf(ta) * sinf(ba);
-    a = (cosf(ba) * (by - ty) - sinf(ba) * (bz - tz)) / d;
-    
-    ey = ty + a * sinf(ta);
-    ez = tz + a * cosf(ta);
-    
-    /* now work back from the eye point to get the lookat location */
-    cz = 0.0;
-    cy = ey - ez * tanf(ElevRad);
-    
-    /* use the distance from the eye to the scene centre to get a measure
-     * of what the far clipping should be.  We then add on a bit more to be 
-     * comfortable */
-    d = sqrtf(ez * ez + (cy - ey) * (cy - ey));
-    
-    gluPerspective(FOV, pState->AspectRatio, 0.1f, d + 20.0f);
-    gluLookAt(0.0, ey, ez, 0.0, cy, cz, 0.0, 1.0, 0.0);
-
-    glMatrixMode(GL_MODELVIEW);
+  Trajectory *t;
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       switch (t->status) {
+       case ATCH:
+         (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
+         break;
+       case THRATCH:
+         (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
+                                        t->pattern == NULL?"":t->pattern);
+         break;
+       case ACTION:
+         if (t->action == CATCH)
+           (void) fprintf(stderr, "%p A %c%cC\n",
+                                        (void*)t, t->posn,
+                                        t->hand ? 'R' : 'L');
+         else
+           (void) fprintf(stderr, "%p A %c%c%c%d\n",
+                                        (void*)t, t->posn,
+                                        t->hand ? 'R' : 'L',
+                                        (t->action == THROW)?'T':'N',
+                                        t->height);
+         break;
+       case LINKEDACTION:
+         (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
+                                        (void*)t, t->posn,
+                                        t->hand?'R':'L',
+                                        (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
+                                        t->height, t->object == NULL?0:t->object->color,
+                                        (void*)t->handlink, (void*)t->balllink);
+         break;
+       case PTHRATCH:
+         (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
+                                        (void*)t, t->posn,
+                                        t->hand?'R':'L',
+                                        (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
+                                        t->height, t->type, t->object == NULL?0:t->object->color,
+                                        t->start, t->finish);
+         break;
+       case BPREDICTOR:
+         (void) fprintf(stderr, "%p B %c      %2d %6lu %6lu %g\n",
+                                        (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
+                                        t->object == NULL?0:t->object->color,
+                                        t->start, t->finish, t->yp.c);
+         break;
+       case PREDICTOR:
+         (void) fprintf(stderr, "%p P %c      %2d %6lu %6lu %g\n",
+                                        (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
+                                        t->object == NULL?0:t->object->color,
+                                        t->start, t->finish, t->yp.c);
+         break;
+       default:
+         (void) fprintf(stderr, "%p: status %d not implemented\n",
+                                        (void*)t, t->status);
+         break;
+       }
+  }
+  (void) fprintf(stderr, "---\n");
 }
+#endif
 
-
-static void ResizeGL(RENDER_STATE* pState, int w, int h)
+static int get_num_balls(const char *j)
 {
-    glViewport(0, 0, w, h);
-    pState->AspectRatio = (float) w / h;
-    SetCamera(pState);
+  int balls = 0;
+  const char *p;
+  int h = 0;
+  if (!j) abort();
+  for (p = j; *p; p++) {
+       if (*p >= '0' && *p <='9') { /* digit */
+         h = 10*h + (*p - '0');
+       } else {
+         if (h > balls) {
+               balls = h;
+         }
+         h = 0;
+       }
+  }
+  return balls;
 }
 
-
-/* Determine the angle at the vertex of a triangle given the length of the
- * three sides. */
-
-static double CosineRule(double a, double b, double c)
+static int
+compare_num_balls(const void *p1, const void *p2)
 {
-    double cosang = (a * a + b * b - c * c) / (2 * a * b);
-    /* If lengths don't form a proper triangle return something sensible.
-     * This typically happens with patterns where the juggler reaches too 
-     * far to get hold of an object. */
-    if (cosang < -1.0 || cosang > 1.0)
-        return 0;
-    else
-        return 180.0 * acos(cosang) / PI;
+  int i, j;
+  i = get_num_balls(((patternstruct*)p1)->pattern);
+  j = get_num_balls(((patternstruct*)p2)->pattern);
+  if (i > j) {
+       return (1);
+  } else if (i < j) {
+       return (-1);
+  } else {
+       return (0);
+  }
 }
 
 
-/* Spheres for the balls are generated by subdividing each triangle face into
- * four smaller triangles.  We start with an octahedron (8 sides) and repeat the
- * process a number of times.  The result is a mesh that can be split into four
- * panels (like beanbags) and is smoother than the normal stacks and slices
- * approach. */
+/**************************************************************************
+ *                        Rendering Functions                             *
+ *                                                                        *
+ **************************************************************************/
 
-static void InterpolateVertex(
-    const float* v1, const float* v2, float t, float* result)
+static int
+show_arms(ModeInfo * mi)
 {
-    result[0] = v1[0] * (1.0f - t) + v2[0] * t;
-    result[1] = v1[1] * (1.0f - t) + v2[1] * t;
-    result[2] = v1[2] * (1.0f - t) + v2[2] * t;
-}
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  unsigned int i, j;
+  Hand side;
+  XPoint a[countof(sp->arm[0][0])];
+  int slices = 12;
+  int thickness = 7;
+  int soffx = 10;
+  int soffy = 11;
+
+  j = 1;
+  for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
+       /* Translate into device coords */
+       for(i = 0; i < countof(a); i++) {
+         a[i].x = (short)(SCENE_WIDTH/2 + sp->arm[j][side][i].x*sp->scale);
+         a[i].y = (short)(SCENE_HEIGHT  - sp->arm[j][side][i].y*sp->scale);
+         if(j == 1)
+               sp->arm[0][side][i] = sp->arm[1][side][i];
+       }
+
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+
+        /* Upper arm */
+        polys += tube (a[2].x - (side == LEFT ? soffx : -soffx), a[2].y + soffy, 0,
+                       a[1].x, a[1].y, ARMLENGTH/2,
+                       thickness, 0, slices,
+                       True, True, MI_IS_WIREFRAME(mi));
+
+        /* Lower arm */
+        polys += tube (a[1].x, a[1].y, ARMLENGTH/2,
+                       a[0].x, a[0].y, ARMLENGTH,
+                       thickness * 0.8, 0, slices,
+                       True, True, MI_IS_WIREFRAME(mi));
+
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+
+        /* Shoulder */
+        glPushMatrix();
+        glTranslatef (a[2].x - (side == LEFT ? soffx : -soffx), 
+                      a[2].y + soffy, 
+                      0);
+        glScalef(9, 9, 9);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
+        /* Elbow */
+        glPushMatrix();
+        glTranslatef (a[1].x, a[1].y, ARMLENGTH/2);
+        glScalef(4, 4, 4);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-static void SetGLVertex(const float* v, float rad)
-{
-    float Len = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+        /* Hand */
+        glPushMatrix();
+        glTranslatef (a[0].x, a[0].y, ARMLENGTH);
+        glScalef(8, 8, 8);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-    if (Len >= 1.0e-10f)
-    {
-        glNormal3f(v[0] / Len, v[1] / Len, v[2] / Len);
-        glVertex3f(rad * v[0] / Len, rad * v[1] / Len, rad * v[2] / Len);
-    }
-    else
-        glVertex3fv(v);
+  }
+  return polys;
 }
 
-
-static void SphereSegment(
-    const float* v1, const float* v2, const float* v3, float r, int Levels)
+static int
+show_figure(ModeInfo * mi, Bool init)
 {
-    int i, j;
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*XPoint p[7];*/
+  int i;
+
+  /*      +-----+ 9
+          |  6  |
+       10 +--+--+
+       2 +---+---+ 3
+          \  5  /
+           \   /
+            \ /
+           1 +
+            / \
+           /   \
+        0 +-----+ 4
+          |     |
+          |     |
+          |     |
+        7 +     + 8
+  */
+
+  /* #### most of this is unused now */
+  static const XPoint figure[] = {
+       { 15,  70}, /* 0  Left Hip */
+       {  0,  90}, /* 1  Waist */
+       { SX, 130}, /* 2  Left Shoulder */
+       {-SX, 130}, /* 3  Right Shoulder */
+       {-15,  70}, /* 4  Right Hip */
+       {  0, 130}, /* 5  Neck */
+       {  0, 140}, /* 6  Chin */
+       { SX,   0}, /* 7  Left Foot */
+       {-SX,   0}, /* 8  Right Foot */
+       {-17, 174}, /* 9  Head1 */
+       { 17, 140}, /* 10 Head2 */
+  };
+  XPoint a[countof(figure)];
+  GLfloat gcolor[4] = { 1, 1, 1, 1 };
+
+  /* Translate into device coords */
+  for(i = 0; i < countof(figure); i++) {
+       a[i].x = (short)(SCENE_WIDTH/2 + (sp->cx + figure[i].x)*sp->scale);
+       a[i].y = (short)(SCENE_HEIGHT - figure[i].y*sp->scale);
+  }
+
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor);
+
+  {
+    GLfloat scale = ((GLfloat) a[10].x - a[9].x) / 2;
+    int slices = 12;
 
-    for (i = 0; i < Levels; i++)
+    glPushMatrix();
     {
-        float A[3], B[3], C[3], D[3];
+      glTranslatef(a[6].x, a[6].y - scale, 0);
+      glScalef(scale, scale, scale);
+
+      /* Head */
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+      glPushMatrix();
+      scale = 0.75;
+      glScalef(scale, scale, scale);
+      glTranslatef(0, 0.3, 0);
+      glPushMatrix();
+      glTranslatef(0, 0, 0.35);
+      polys += tube (0, 0, 0,
+                     0, 1.1, 0,
+                     0.64, 0,
+                     slices, True, True, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+      glScalef(0.9, 0.9, 1);
+      polys += unit_sphere(2*slices, 2*slices, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+
+      /* Neck */
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+      glTranslatef(0, 1.1, 0);
+      glPushMatrix();
+      scale = 0.35;
+      glScalef(scale, scale, scale);
+      polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+
+      /* Torso */
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+      glTranslatef(0, 1.1, 0);
+      glPushMatrix();
+      glScalef(0.9, 1.0, 0.9);
+      polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+
+      /* Belly */
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+      glTranslatef(0, 1.0, 0);
+      glPushMatrix();
+      scale = 0.6;
+      glScalef(scale, scale, scale);
+      polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+
+      /* Hips */
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+      glTranslatef(0, 0.8, 0);
+      glPushMatrix();
+      scale = 0.85;
+      glScalef(scale, scale, scale);
+      polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+      glPopMatrix();
+
+
+      /* Legs */
+      glTranslatef(0, 0.7, 0);
+
+      for (i = -1; i <= 1; i += 2) {
+        glPushMatrix();
+
+        glRotatef (i*10, 0, 0, 1);
+        glTranslatef(-i*0.65, 0, 0);
         
-        InterpolateVertex(v3, v1, (float) i / Levels, D);
-        InterpolateVertex(v3, v1, (float)(i + 1) / Levels, A);
-        InterpolateVertex(v3, v2, (float)(i + 1) / Levels, B);
-        InterpolateVertex(v3, v2, (float) i / Levels, C);
+        /* Hip socket */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+        scale = 0.45;
+        glScalef(scale, scale, scale);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+
+        /* Thigh */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+        glPushMatrix();
+        glTranslatef(0, 0.6, 0);
+        polys += tube (0, 0, 0,
+                       0, 3.5, 0,
+                       1, 0,
+                       slices, True, True, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-        glBegin(GL_TRIANGLE_STRIP);
+        /* Knee */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+        glPushMatrix();
+        glTranslatef(0, 4.4, 0);
+        scale = 0.7;
+        glScalef(scale, scale, scale);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-        SetGLVertex(B, r);
-        SetGLVertex(C, r);
-        
-        for (j = 1; j <= i; j++)
-        {
-            float v[3];
+        /* Calf */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+        glPushMatrix();
+        glTranslatef(0, 4.7, 0);
+        polys += tube (0, 0, 0,
+                       0, 4.7, 0,
+                       0.8, 0,
+                       slices, True, True, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-            InterpolateVertex(B, A, (float) j / (i + 1), v);
-            SetGLVertex(v, r);
+        /* Ankle */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
+        glPushMatrix();
+        glTranslatef(0, 9.7, 0);
+        scale = 0.5;
+        glScalef(scale, scale, scale);
+        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-            InterpolateVertex(C, D, (float) j / i, v);
-            SetGLVertex(v, r);
-        }
+        /* Foot */
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
+        glPushMatrix();
+        glRotatef (-i*10, 0, 0, 1);
+        glTranslatef(-i*1.75, 9.7, 0.9);
+
+        glScalef (0.4, 1, 1);
+        polys += tube (0, 0, 0,
+                       0, 0.6, 0,
+                       1.9, 0,
+                       slices*4, True, True, MI_IS_WIREFRAME(mi));
+        glPopMatrix();
 
-        SetGLVertex(A, r);
-        
-        glEnd();
+        glPopMatrix();
+      }
     }
+    glPopMatrix();
+  }
+
+  sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
+  sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
+  if(init) {
+       /* Initialise arms */
+       unsigned int i;
+       for(i = 0; i < 2; i++){
+         sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
+         sp->arm[i][LEFT][ELBOW].x = figure[2].x;
+         sp->arm[i][LEFT][ELBOW].y = figure[1].y;
+         sp->arm[i][LEFT][HAND].x = figure[0].x;
+         sp->arm[i][LEFT][HAND].y = figure[1].y;
+         sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
+         sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
+         sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
+         sp->arm[i][RIGHT][HAND].x = figure[4].x;
+         sp->arm[i][RIGHT][HAND].y = figure[1].y;
+       }
+  }
+  return polys;
 }
 
+typedef struct { GLfloat x, y, z; } XYZ;
 
-/* OK, this function is a bit of misnomer, it only draws half a sphere.  Indeed
- * it draws two panels and allows us to colour this one way,  then draw the
- * same shape again rotated 90 degrees in a different colour.  Resulting in what
- * looks like a four-panel beanbag in two complementary colours. */
-static void DrawSphere(float rad)
+/* lifted from sphere.c */
+static int
+striped_unit_sphere (int stacks, int slices, 
+                     int stripes, 
+                     GLfloat *color1, GLfloat *color2,
+                     int wire_p)
 {
-    int Levels = 4;
-    float v1[3], v2[3], v3[3];
-    
-    v1[0] = 1.0f, v1[1] = 0.0f; v1[2] = 0.0f;
-    v2[0] = 0.0f, v2[1] = 1.0f; v2[2] = 0.0f;
-    v3[0] = 0.0f, v3[1] = 0.0f; v3[2] = 1.0f;
-    SphereSegment(v1, v2, v3, rad, Levels);
-    
-    v2[1] = -1.0f;
-    SphereSegment(v2, v1, v3, rad, Levels);
-    
-    v1[0] = v3[2] = -1.0f;
-    SphereSegment(v2, v1, v3, rad, Levels);
-
-    v2[1] = 1.0f;
-    SphereSegment(v1, v2, v3, rad, Levels);
-}
-
+  int polys = 0;
+  int i,j;
+  double theta1, theta2, theta3;
+  XYZ e, p;
+  XYZ la = { 0, 0, 0 }, lb = { 0, 0, 0 };
+  XYZ c = {0, 0, 0};  /* center */
+  double r = 1.0;     /* radius */
+  int stacks2 = stacks * 2;
+
+  if (r < 0)
+    r = -r;
+  if (slices < 0)
+    slices = -slices;
+
+  if (slices < 4 || stacks < 2 || r <= 0)
+    {
+      glBegin (GL_POINTS);
+      glVertex3f (c.x, c.y, c.z);
+      glEnd();
+      return 1;
+    }
 
-static void DrawRing(void)
-{
-    const int Facets = 22;
-    const float w = 0.1f;
-    GLUquadric* pQuad = gluNewQuadric();
-    glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
-    glTranslatef(0.0f, 0.0f, -w / 2.0f);
+  glFrontFace(GL_CW);
 
-    gluCylinder(pQuad, 1.0f, 1.0f, w, Facets, 1);
-    gluQuadricOrientation(pQuad, GLU_INSIDE);
+  for (j = 0; j < stacks; j++)
+    {
+      theta1 = j       * (M_PI+M_PI) / stacks2 - M_PI_2;
+      theta2 = (j + 1) * (M_PI+M_PI) / stacks2 - M_PI_2;
 
-    gluCylinder(pQuad, 0.7f, 0.7f, w, Facets, 1);
-    gluQuadricOrientation(pQuad, GLU_OUTSIDE);
+      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
+                    ((j == 0 || j == stacks-1 ||
+                     j % (stacks / (stripes+1)))
+                     ? color1 : color2));
 
-    glTranslatef(0.0f, 0.0f, w);
-    gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
+      glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
+      for (i = 0; i <= slices; i++)
+        {
+          theta3 = i * (M_PI+M_PI) / slices;
 
-    glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
-    glTranslatef(0.0f, 0.0f, w);
-    gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
+          if (wire_p && i != 0)
+            {
+              glVertex3f (lb.x, lb.y, lb.z);
+              glVertex3f (la.x, la.y, la.z);
+            }
 
-    gluDeleteQuadric(pQuad);
+          e.x = cos (theta2) * cos(theta3);
+          e.y = sin (theta2);
+          e.z = cos (theta2) * sin(theta3);
+          p.x = c.x + r * e.x;
+          p.y = c.y + r * e.y;
+          p.z = c.z + r * e.z;
+
+          glNormal3f (e.x, e.y, e.z);
+          glTexCoord2f (i       / (double)slices,
+                        2*(j+1) / (double)stacks2);
+          glVertex3f (p.x, p.y, p.z);
+          if (wire_p) la = p;
+
+          e.x = cos(theta1) * cos(theta3);
+          e.y = sin(theta1);
+          e.z = cos(theta1) * sin(theta3);
+          p.x = c.x + r * e.x;
+          p.y = c.y + r * e.y;
+          p.z = c.z + r * e.z;
+
+          glNormal3f (e.x, e.y, e.z);
+          glTexCoord2f (i   / (double)slices,
+                        2*j / (double)stacks2);
+          glVertex3f (p.x, p.y, p.z);
+          if (wire_p) lb = p;
+          polys++;
+        }
+      glEnd();
+    }
+  return polys;
 }
 
 
-/* The club follows a 'circus club' design i.e. it has stripes running down the
- * body.  The club is draw such that the one stripe uses the current material
- * and the second stripe the standard silver colour. */
 
-static void DrawClub(void)
+static int
+show_ball(ModeInfo *mi, unsigned long color, Trace *s)
 {
-    const float r[4] = {0.06f, 0.1f, 0.34f, 0.34f / 2.0f};
-    const float z[4] = {-0.4f, 0.6f, 1.35f, 2.1f};
-    float na[4];
-    const int n = 18;
-    int i, j;
-    GLUquadric* pQuad;
-
-    na[0] = (float) atan((r[1] - r[0]) / (z[1] - z[0]));
-    na[1] = (float) atan((r[2] - r[1]) / (z[2] - z[1]));
-    na[2] = (float) atan((r[3] - r[1]) / (z[3] - z[1]));
-    na[3] = (float) atan((r[3] - r[2]) / (z[3] - z[2]));
-
-    for (i = 0; i < n; i += 2)
-    {
-        float a1 = i * PI * 2.0f / n;
-        float a2 = (i + 1) * PI * 2.0f / n;
-
-        glBegin(GL_TRIANGLE_STRIP);
-            for (j = 1; j < 4; j++)
-            {
-                glNormal3f(cosf(na[j]) * cosf(a1),
-                    cosf(na[j]) * sinf(a1), sinf(na[j]));
-
-                glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*int offset = (int)(s->angle*64*180/M_PI);*/
+  short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
+  short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
+  GLfloat gcolor1[4] = { 0, 0, 0, 1 };
+  GLfloat gcolor2[4] = { 0, 0, 0, 1 };
+  int slices = 24;
+
+  /* Avoid wrapping */
+  if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+  gcolor1[0] = mi->colors[color].red   / 65536.0;
+  gcolor1[1] = mi->colors[color].green / 65536.0;
+  gcolor1[2] = mi->colors[color].blue  / 65536.0;
+
+  gcolor2[0] = gcolor1[0] / 3;
+  gcolor2[1] = gcolor1[1] / 3;
+  gcolor2[2] = gcolor1[2] / 3;
+
+  {
+    GLfloat scale = BALLRADIUS;
+    glPushMatrix();
+    glTranslatef(x, y, 0);
+    glScalef(scale, scale, scale);
 
-                glNormal3f(cosf(na[j]) * cosf(a2),
-                    cosf(na[j]) * sinf(a2),    sinf(na[j]));
+    glRotatef (s->angle / M_PI*180, 1, 1, 0);
 
-                glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
-            }
-        glEnd();
-    }
+    polys += striped_unit_sphere (slices, slices, s->divisions, 
+                                  gcolor1, gcolor2, MI_IS_WIREFRAME(mi));
+    glPopMatrix();
+  }
+  return polys;
+}
 
-    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, HandleCol);
+static int
+show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
+{
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*int offset = (int)(s->angle*64*180/M_PI);*/
+  short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
+  short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
+  double radius = 12 * sp->scale;
+  GLfloat gcolor1[4] = { 0, 0, 0, 1 };
+  GLfloat gcolor2[4] = { 1, 1, 1, 1 };
+  int slices = 16;
+  int divs = s->divisions;
+  divs = 4;
+
+  /*    6   6
+         +-+
+        /   \
+     4 +-----+ 7
+      ////////\
+   3 +---------+ 8
+   2 +---------+ 9
+      |///////|
+    1 +-------+ 10
+       |     |
+       |     |
+        |   |
+        |   |
+         | |
+         | |
+         +-+
+        0  11  */
+
+  /* Avoid wrapping */
+  if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+  gcolor1[0] = mi->colors[color].red   / 65536.0;
+  gcolor1[1] = mi->colors[color].green / 65536.0;
+  gcolor1[2] = mi->colors[color].blue  / 65536.0;
+
+  {
+    GLfloat scale = radius;
+    glPushMatrix();
+    glTranslatef(x, y, 0);
+    glScalef(scale, scale, scale);
 
-    for (i = 1; i < n; i += 2)
-    {
-        float a1 = i * PI * 2.0f / n;
-        float a2 = (i + 1) * PI * 2.0f / n;
+    glTranslatef (0, 0, 2);  /* put end of handle in hand */
 
-        glBegin(GL_TRIANGLE_STRIP);
-            for (j = 1; j < 4; j++)
-            {
-                glNormal3f(cosf(na[j]) * cosf(a1),
-                    cosf(na[j]) * sinf(a1),    sinf(na[j]));
+    glRotatef (s->angle / M_PI*180, 1, 0, 0);
 
-                glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
+    glPushMatrix();
+    glScalef (0.5, 1, 0.5);
+    polys += striped_unit_sphere (slices, slices, divs, gcolor2, gcolor1, 
+                                  MI_IS_WIREFRAME(mi));
+    glPopMatrix();
+    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
+    polys += tube (0, 0, 0,
+                   0, 2, 0,
+                   0.2, 0,
+                   slices, True, True, MI_IS_WIREFRAME(mi));
 
-                glNormal3f(cosf(na[j]) * cosf(a2),
-                    cosf(na[j]) * sinf(a2), sinf(na[j]));
+    glTranslatef (0, 2, 0);
+    glScalef (0.25, 0.25, 0.25);
+    polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
 
-                glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
-            }
-        glEnd();
-    }
+    glPopMatrix();
+  }
+  return polys;
+}
 
-    pQuad = gluNewQuadric();
-    glTranslatef(0.0f, 0.0f, z[0]);
-    gluCylinder(pQuad, r[0], r[1], z[1] - z[0], n, 1);
 
-    glTranslatef(0.0f, 0.0f, z[3] - z[0]);
-    gluDisk(pQuad, 0.0, r[3], n, 1);
-    glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
-    glTranslatef(0.0f, 0.0f, z[3] - z[0]);
-    gluDisk(pQuad, 0.0, r[0], n, 1);
-    gluDeleteQuadric(pQuad);
+static int
+show_torch(ModeInfo *mi, unsigned long color, Trace *s)
+{
+  int polys = 0;
+#if 0
+       jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+       XPoint head, tail, last;
+       DXPoint dhead, dlast;
+       const double sa = sin(s->angle);
+       const double ca = cos(s->angle);
+
+       const double TailLen = -24;
+       const double HeadLen = 16;
+       const short Width   = (short)(5 * sqrt(sp->scale));
+
+       /*
+      +///+ head
+    last  |
+          |
+          |
+          |
+          |
+          + tail
+       */
+
+       dhead.x = s->x + HeadLen * PERSPEC * sa;
+       dhead.y = s->y - HeadLen * ca;
+
+       if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
+         dlast = s->dlast;
+       } else { /* Store 'last' so we can use it later when s->prev has
+                               gone */
+         if(s->prev != s->next) {
+               dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
+               dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
+         } else {
+               dlast = dhead;
+         }
+         s->dlast = dlast;
+       }
+
+       /* Avoid wrapping (after last is stored) */
+       if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+       head.x = (short)(SCENE_WIDTH/2 + dhead.x*sp->scale);
+       head.y = (short)(SCENE_HEIGHT - dhead.y*sp->scale);
+
+       last.x = (short)(SCENE_WIDTH/2 + dlast.x*sp->scale);
+       last.y = (short)(SCENE_HEIGHT - dlast.y*sp->scale);
+
+       tail.x = (short)(SCENE_WIDTH/2 +
+                                        (s->x + TailLen * PERSPEC * sa)*sp->scale );
+       tail.y = (short)(SCENE_HEIGHT - (s->y - TailLen * ca)*sp->scale );
+
+       if(color != MI_BLACK_PIXEL(mi)) {
+         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
+         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
+                                                Width, LineSolid, CapRound, JoinRound);
+         draw_line(mi, head.x, head.y, tail.x, tail.y);
+       }
+       XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
+       XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
+                                          Width * 2, LineSolid, CapRound, JoinRound);
+
+       draw_line(mi, head.x, head.y, last.x, last.y);
+
+#endif /* 0 */
+   return polys;
 }
 
 
-/* In total 6 display lists are used.  There are created based on the DL_
- * constants defined earlier.  The function returns the index of the first
- * display list, all others can be calculated based on an offset from there. */
-
-static int InitGLDisplayLists(void)
+static int
+show_knife(ModeInfo *mi, unsigned long color, Trace *s)
 {
-    int s = glGenLists(6);
-    GLUquadric* pQuad;
-
-    glNewList(s + DL_BALL, GL_COMPILE);
-    DrawSphere(BallRad);
-    glEndList();
-
-    glNewList(s + DL_CLUB, GL_COMPILE);
-    DrawClub();
-    glEndList();
-
-    glNewList(s + DL_RING, GL_COMPILE);
-    DrawRing();
-    glEndList();
-    
-    pQuad =  gluNewQuadric();
-    gluQuadricNormals(pQuad, GLU_SMOOTH);    
-    
-    glNewList(s + DL_TORSO, GL_COMPILE);
-        glPushMatrix();
-            glTranslatef(ShoulderPos[0], ShoulderPos[1], -ShoulderPos[2]);
-            glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
-            gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[0] * 2, 18, 1);
-        glPopMatrix();
-
-        glPushMatrix();
-            glTranslatef(0.0f, -1.0f, -ShoulderPos[2]);
-            glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
-            gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[1] + 1.0f, 18, 1);
-            glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
-            gluDisk(pQuad, 0.0, 0.3, 18, 1);
-        glPopMatrix();
-        
-        /* draw the head */
-        glPushMatrix();
-            glTranslatef(0.0f, ShoulderPos[1] + 1.0f, -ShoulderPos[2]);
-            glRotatef(-30.0f, 1.0f, 0.0f, 0.0f);
-            gluCylinder(pQuad, 0.5, 0.5, 0.3, 15, 1);
-            
-            glPushMatrix();
-                glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
-                glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
-                gluDisk(pQuad, 0.0, 0.5, 15, 1);
-            glPopMatrix(); 
-                
-            glTranslatef(0.0f, 0.0f, .3f);
-            gluDisk(pQuad, 0.0, 0.5, 15, 1);
-        glPopMatrix();        
-    glEndList();
-    
-    glNewList(s + DL_UPPERARM, GL_COMPILE);
-        gluQuadricNormals(pQuad, GLU_SMOOTH);
-        gluQuadricDrawStyle(pQuad, GLU_FILL);
-        gluSphere(pQuad, 0.3, 12, 8);
-
-        gluCylinder(pQuad, 0.3, 0.3, UArmLen, 12, 1); 
-        glTranslatef(0.0f, 0.0f, UArmLen);
-        gluSphere(pQuad, 0.3, 12, 8);
-    glEndList();
-
-    glNewList(s + DL_FOREARM, GL_COMPILE);
-        gluCylinder(pQuad, 0.3, 0.3 / 2.0f, LArmLen, 12, 1);
-        glTranslatef(0.0f, 0.0f, LArmLen);
-        gluDisk(pQuad, 0, 0.3 / 2.0f, 18, 1);
-    glEndList();
-
-    gluDeleteQuadric(pQuad);
-    return s;
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*int offset = (int)(s->angle*64*180/M_PI);*/
+  short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
+  short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
+  GLfloat gcolor1[4] = { 0, 0, 0, 1 };
+  GLfloat gcolor2[4] = { 1, 1, 1, 1 };
+  int slices = 8;
+
+  /* Avoid wrapping */
+  if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+  gcolor1[0] = mi->colors[color].red   / 65536.0;
+  gcolor1[1] = mi->colors[color].green / 65536.0;
+  gcolor1[2] = mi->colors[color].blue  / 65536.0;
+
+  glPushMatrix();
+  glTranslatef(x, y, 0);
+  glScalef (2, 2, 2);
+
+  glTranslatef (0, 0, 2);  /* put end of handle in hand */
+  glRotatef (s->angle / M_PI*180, 1, 0, 0);
+
+  glScalef (0.3, 1, 1);  /* flatten blade */
+
+  glTranslatef(0, 6, 0);
+  glRotatef (180, 1, 0, 0);
+
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
+  polys += tube (0, 0, 0,
+                 0, 10, 0,
+                 1, 0,
+                 slices, True, True, MI_IS_WIREFRAME(mi));
+
+  glTranslatef (0, 12, 0);
+  glScalef (0.7, 10, 0.7);
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
+  polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
+
+  glPopMatrix();
+  return polys;
 }
 
 
-/* Drawing the arm requires connecting the upper and fore arm between the
- * shoulder and hand position.  Thinking about things kinematically by treating
- * the shoulder and elbow as ball joints then, provided the arm can stretch far
- * enough, there's a infnite number of ways to position the elbow.  Basically
- * it's possible to fix and hand and shoulder and then rotate the elbow a full
- * 360 degrees.  Clearly human anatomy isn't like this and picking a natural
- * elbow position can be complex.  We chicken out and assume that poking the
- * elbow out by 20 degrees from the lowest position gives a reasonably looking
- * orientation. */
-
-static void DrawArm(RENDER_STATE* pState, float TimePos, int Left)
+static int
+show_ring(ModeInfo *mi, unsigned long color, Trace *s)
 {
-    POS Pos;
-    float x, y, len, len2, ang, ang2;
-    
-    GetHandPosition(pState->pPattern, Left, TimePos, &Pos);
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*int offset = (int)(s->angle*64*180/M_PI);*/
+  short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
+  short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
+  double radius = 12 * sp->scale;
+  GLfloat gcolor1[4] = { 0, 0, 0, 1 };
+  GLfloat gcolor2[4] = { 0, 0, 0, 1 };
+  int slices = 24;
+  int i, j;
+  int wire_p = MI_IS_WIREFRAME(mi);
+  GLfloat width = M_PI * 2 / slices;
+  GLfloat ra = 1.0;
+  GLfloat rb = 0.7;
+  GLfloat thickness = 0.15;
+
+  /* Avoid wrapping */
+  if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+  gcolor1[0] = mi->colors[color].red   / 65536.0;
+  gcolor1[1] = mi->colors[color].green / 65536.0;
+  gcolor1[2] = mi->colors[color].blue  / 65536.0;
+
+  gcolor2[0] = gcolor1[0] / 3;
+  gcolor2[1] = gcolor1[1] / 3;
+  gcolor2[2] = gcolor1[2] / 3;
+
+  glPushMatrix();
+  glTranslatef(0, 0, 12);  /* back of ring in hand */
+
+  glTranslatef(x, y, 0);
+  glScalef(radius, radius, radius);
+
+  glRotatef (90, 0, 1, 0);
+  glRotatef (s->angle / M_PI*180, 0, 0, 1);
+
+  glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
+
+  /* discs */
+  for (j = -1; j <= 1; j += 2)
+    {
+      GLfloat z = j * thickness/2;
+      glFrontFace (j < 0 ? GL_CCW : GL_CW);
+      glNormal3f (0, 0, j*1);
+      glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+      for (i = 0; i < slices + (wire_p ? 0 : 1); i++) {
+        glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
+                      (i % (slices/3) ? gcolor1 : gcolor2));
+        GLfloat th = i * width;
+        GLfloat cth = cos(th);
+        GLfloat sth = sin(th);
+        glVertex3f (cth * ra, sth * ra, z);
+        glVertex3f (cth * rb, sth * rb, z);
+        polys++;
+      }
+      glEnd();
+    }
 
-    x = Pos.x + (Left ? -ShoulderPos[0] : ShoulderPos[0]);
-    y = Pos.y - ShoulderPos[1];
+  /* outer ring */
+  glFrontFace (GL_CCW);
+  glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+  for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
+    {
+      GLfloat th = i * width;
+      GLfloat cth = cos(th);
+      GLfloat sth = sin(th);
+      glNormal3f (cth, sth, 0);
+      glVertex3f (cth * ra, sth * ra, thickness/2);
+      glVertex3f (cth * ra, sth * ra, -thickness/2);
+      polys++;
+    }
+  glEnd();
 
+  /* inner ring */
+  glFrontFace (GL_CW);
+  glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
+  for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
+    {
+      GLfloat th = i * width;
+      GLfloat cth = cos(th);
+      GLfloat sth = sin(th);
+      glNormal3f (-cth, -sth, 0);
+      glVertex3f (cth * rb, sth * ra, thickness/2);
+      glVertex3f (cth * rb, sth * ra, -thickness/2);
+      polys++;
+    }
+  glEnd();
 
-    len = sqrtf(x * x + y * y + ShoulderPos[2] * ShoulderPos[2]);
-    len2 = sqrtf(x * x + ShoulderPos[2] * ShoulderPos[2]);
+  glFrontFace (GL_CCW);
+  glPopMatrix();
+  return polys;
+}
 
-    ang = (float) CosineRule(UArmLen, len, LArmLen);
-    ang2 = (float) CosineRule(UArmLen, LArmLen, len);
 
-    if (ang == 0.0 && ang2 == 0)
-        ang2 = 180.0;
+static int
+show_bball(ModeInfo *mi, unsigned long color, Trace *s)
+{
+  int polys = 0;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  /*int offset = (int)(s->angle*64*180/M_PI);*/
+  short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
+  short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
+  double radius = 12 * sp->scale;
+  GLfloat gcolor1[4] = { 0, 0, 0, 1 };
+  GLfloat gcolor2[4] = { 0, 0, 0, 1 };
+  int slices = 16;
+  int i, j;
+
+  /* Avoid wrapping */
+  if(s->y*sp->scale >  SCENE_HEIGHT * 2) return 0;
+
+  gcolor1[0] = mi->colors[color].red   / 65536.0;
+  gcolor1[1] = mi->colors[color].green / 65536.0;
+  gcolor1[2] = mi->colors[color].blue  / 65536.0;
+
+  {
+    GLfloat scale = radius;
+    glPushMatrix();
 
+    glTranslatef(0, -6, 5);  /* position on top of hand */
+
+    glTranslatef(x, y, 0);
+    glScalef(scale, scale, scale);
+    glRotatef (s->angle / M_PI*180, 1, 0, 1);
+
+    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
+    polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
+
+    glRotatef (90, 0, 0, 1);
+    glTranslatef (0, 0, 0.81);
+    glScalef(0.15, 0.15, 0.15);
+    glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
+    for (i = 0; i < 3; i++) {
+      glPushMatrix();
+      glTranslatef (0, 0, 1);
+      glRotatef (360 * i / 3, 0, 0, 1);
+      glTranslatef (2, 0, 0);
+      glRotatef (18, 0, 1, 0);
+      glBegin (MI_IS_WIREFRAME(mi) ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
+      glVertex3f (0, 0, 0);
+      for (j = slices; j >= 0; j--) {
+        GLfloat th = j * M_PI*2 / slices;
+        glVertex3f (cos(th), sin(th), 0);
+        polys++;
+      }
+      glEnd();
+      glPopMatrix();
+    }
 
-    glPushMatrix();
-        glTranslatef(Left ? ShoulderPos[0] : -ShoulderPos[0], ShoulderPos[1],
-            -ShoulderPos[2]);
-        glRotatef((float)(180.0f * asin(x / len2) / 3.14f), 0.0f, 1.0f, 0.0f);
-        glRotatef((float)(-180.f * asin(y / len) / 3.14), 1.0f, 0.0f, 0.0f);
-        glRotatef(Left ? 20.0f : -20.0f, 0.0f, 0.0f, 1.0f);
-        glRotatef((float) ang, 1.0f, 0.0f, 0.0f);
-        glCallList(DL_UPPERARM + pState->DLStart);
-
-        glRotatef((float)(ang2 - 180.0), 1.0f, 0.0f, 0.f);
-        glCallList(DL_FOREARM + pState->DLStart);
     glPopMatrix();
+  }
+  return polys;
 }
 
 
-static void DrawGLScene(RENDER_STATE* pState)
-{
-    float Time = pState->Time;
-    int nCols = sizeof(Cols) / sizeof(Cols[0]);
-    int i;
+/**************************************************************************
+ *                    Public Functions                                    *
+ *                                                                        *
+ **************************************************************************/
 
-    PATTERN_INFO* pPattern = pState->pPattern;
 
-    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+ENTRYPOINT void
+release_juggle (ModeInfo * mi)
+{
+  if (juggles != NULL) {
+       int screen;
+
+       for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
+         free_juggle(&juggles[screen]);
+       free(juggles);
+       juggles = (jugglestruct *) NULL;
+  }
+}
 
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-    glTranslatef(5.0f * sinf(pState->TranslateAngle), 0.0f, 0.0f);
+/* FIXME: refill_juggle currently just appends new throws to the
+ * programme.  This is fine if the programme is empty, but if there
+ * are still some trajectories left then it really should take these
+ * into account */
 
-    gltrackball_rotate (pState->trackball);
+static void
+refill_juggle(ModeInfo * mi)
+{
+  jugglestruct *sp = NULL;
+  int i;
+
+  if (juggles == NULL)
+       return;
+  sp = &juggles[MI_SCREEN(mi)];
+
+  /* generate pattern */
+
+  if (pattern == NULL) {
+
+#define MAXPAT 10
+#define MAXREPEAT 300
+#define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
+#define POSITION_BIAS 20 /* larger makes hand movements less likely */
+
+       int count = 0;
+       while (count < MI_CYCLES(mi)) {
+         char buf[MAXPAT * 3 + 3], *b = buf;
+         int maxseen = 0;
+         int l = NRAND(MAXPAT) + 1;
+         int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
+
+         { /* vary number of balls */
+               int new_balls = sp->num_balls;
+               int change;
+
+               if (new_balls == 2) /* Do not juggle 2 that often */
+                 change = NRAND(2 + CHANGE_BIAS / 4);
+               else
+                 change = NRAND(2 + CHANGE_BIAS);
+               switch (change) {
+               case 0:
+                 new_balls++;
+                 break;
+               case 1:
+                 new_balls--;
+                 break;
+               default:
+                 break; /* NO-OP */
+               }
+               if (new_balls < sp->patternindex.minballs) {
+                 new_balls += 2;
+               }
+               if (new_balls > sp->patternindex.maxballs) {
+                 new_balls -= 2;
+               }
+               if (new_balls < sp->num_balls) {
+                 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
+                       return;
+               }
+               sp->num_balls = new_balls;
+         }
+
+         count += t;
+         if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
+               /* Pick from PortFolio */
+               int p = sp->patternindex.index[sp->num_balls].start +
+                 NRAND(sp->patternindex.index[sp->num_balls].number);
+               if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
+                 return;
+         } else {
+               /* Invent a new pattern */
+               *b++='[';
+               for(i = 0; i < l; i++){
+                 int n, m;
+                 do { /* Triangular Distribution => high values more likely */
+                       m = NRAND(sp->num_balls + 1);
+                       n = NRAND(sp->num_balls + 1);
+                 } while(m >= n);
+                 if (n == sp->num_balls) {
+                       maxseen = 1;
+                 }
+                 switch(NRAND(5 + POSITION_BIAS)){
+                 case 0:            /* Outside throw */
+                       *b++ = '+'; break;
+                 case 1:            /* Cross throw */
+                       *b++ = '='; break;
+                 case 2:            /* Cross catch */
+                       *b++ = '&'; break;
+                 case 3:            /* Cross throw and catch */
+                       *b++ = 'x'; break;
+                 case 4:            /* Bounce */
+                       *b++ = '_'; break;
+                 default:
+                       break;             /* Inside throw (default) */
+                 }
+
+                 *b++ = n + '0';
+                 *b++ = ' ';
+               }
+               *b++ = ']';
+               *b = '\0';
+               if (maxseen) {
+                 if (!program(mi, buf, NULL, t))
+                       return;
+               }
+         }
+       }
+  } else { /* pattern supplied in height or 'a' notation */
+       if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
+         return;
+  }
+
+  adam(sp);
+
+  name(sp);
+
+  if (!part(sp))
+       return;
+
+  lob(mi);
+
+  clap(sp);
+
+  positions(sp);
+
+  if (!projectile(sp)) {
+       free_juggle(sp);
+       return;
+  }
+
+  hands(sp);
+#ifdef DEBUG
+  if(MI_IS_DEBUG(mi)) dump(sp);
+#endif
+}
 
-    glRotatef(pState->SpinAngle, 0.0f, 1.0f, 0.0f);
-    glTranslatef(0.0, 0.0, -1.0f);
+static void
+change_juggle(ModeInfo * mi)
+{
+  jugglestruct *sp = NULL;
+  Trajectory *t;
 
-    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, DiffCol);
-    glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SpecCol);
-    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 60.0f);
+  if (juggles == NULL)
+       return;
+  sp = &juggles[MI_SCREEN(mi)];
 
-    for (i = 0; i < pPattern->Objects; i++)
-    {
-        POS ObjPos;
-        
-        glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Cols[i % nCols]);
-        glPushMatrix();
+  /* Strip pending trajectories */
+  for (t = sp->head->next; t != sp->head; t = t->next) {
+       if(t->start > sp->time || t->finish < sp->time) {
+         Trajectory *n = t;
+         t=t->prev;
+         trajectory_destroy(n);
+       }
+  }
 
-        switch (pPattern->pObjectInfo[i].ObjectType)
-        {
-            case OBJECT_CLUB:
-                GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos);
-                glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);
-                glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f);
-                glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f);
-                glTranslatef(0.0f, 0.0f, -1.0f);
-                glCallList(DL_CLUB + pState->DLStart);
-                break;
-
-            case OBJECT_RING:
-                GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos);
-                glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);
-                glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f);
-                glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f);
-                glCallList(DL_RING + pState->DLStart);
-                break;
-
-            default:
-                GetObjectPosition(pPattern, i, Time, 0.0f, &ObjPos);
-                glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);        
-                glRotatef(ObjPos.Rot, 0.6963f, 0.6963f, 0.1742f);
-                glCallList(DL_BALL + pState->DLStart);
-                glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
-                glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 
-                    AltCols[i % nCols]);
-                glCallList(DL_BALL + pState->DLStart);
-                break;
-        }
+  /* Pick the current object theme */
+  sp->objtypes = choose_object();
 
-        glPopMatrix();
-    }
+  refill_juggle(mi);
 
-    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, BodyCol);
-    glCallList(DL_TORSO + pState->DLStart);
-    DrawArm(pState, Time, 1);
-    DrawArm(pState, Time, 0);
+  mi->polygon_count += show_figure(mi, True);
 }
 
 
-static int RandInRange(int Min, int Max)
+ENTRYPOINT void
+reshape_juggle (ModeInfo *mi, int width, int height)
 {
-    return Min + random() % (1 + Max - Min);
-}
+  GLfloat h = (GLfloat) height / (GLfloat) width;
 
+  glViewport (0, 0, (GLint) width, (GLint) height);
 
-static void UpdatePattern(
-    RENDER_STATE* pState, int MinBalls, int MaxBalls, 
-    int MinHeightInc, int MaxHeightInc)
-{
-    if (pState->pPattern != NULL)
-        ReleasePatternInfo(pState->pPattern);
-    
-    pState->pPattern = (PATTERN_INFO*) malloc(sizeof(PATTERN_INFO));
-    
-    if ((random() % 3) == 1)
-    {    
-        int ExtSiteLen;
-        int n = random() % (sizeof(PatternText) / sizeof(PatternText[0]));
-        EXT_SITE_INFO* pExtInfo = ParsePattern(PatternText[n], &ExtSiteLen);
-        InitPatternInfo(pState->pPattern, NULL, pExtInfo, ExtSiteLen);
-        free(pExtInfo);
-    }
-    else
-    {
-        int* pRand;
-        int ballcount, maxweight;
-        const int RandPatternLen = 1500;
-        
-        ballcount = RandInRange(MinBalls, MaxBalls);
-        maxweight = ballcount  + RandInRange(MinHeightInc, MaxHeightInc);
-        
-        pRand = Generate(RandPatternLen, maxweight, ballcount);
-        InitPatternInfo(pState->pPattern, pRand, NULL, RandPatternLen);
-        free(pRand);
-    }
-    
-    pState->CameraElev = 50.0f - random() % 90;
-    pState->TranslateAngle = random() % 360;
-    pState->SpinAngle = random() % 360;
-    pState->Time = 50.0f;
-    SetCamera(pState);
-}
-
+  glMatrixMode(GL_PROJECTION);
+  glLoadIdentity();
+  gluPerspective (30.0, 1/h, 1.0, 100.0);
 
-/*******************************************************************************
- *
- *  XScreenSaver Configuration
- *
- ******************************************************************************/
+  glMatrixMode(GL_MODELVIEW);
+  glLoadIdentity();
+  gluLookAt( 0.0, 0.0, 30.0,
+             0.0, 0.0, 0.0,
+             0.0, 1.0, 0.0);
 
-typedef struct
-{
-    GLXContext* glxContext;
-    RENDER_STATE RenderState;
-    float CurrentFrameRate;
-    unsigned FramesSinceSync;
-    unsigned LastSyncTime;
-} JUGGLER3D_CONFIG;
-
-
-#define DEF_MAX_OBJS           "8"
-#define DEF_MIN_OBJS           "3"
-#define DEF_MAX_HINC           "6"
-#define DEF_MIN_HINC           "2"
-#define DEF_JUGGLE_SPEED       "2.2"
-#define DEF_TRANSLATE_SPEED    "0.1"
-#define DEF_SPIN_SPEED         "20.0"
-
-static JUGGLER3D_CONFIG* pConfigInfo = NULL;
-static int MaxObjects;
-static int MinObjects;
-static int MaxHeightInc;
-static int MinHeightInc;
-static float SpinSpeed;
-static float TranslateSpeed;
-static float JuggleSpeed;
-
-static XrmOptionDescRec opts[] =
-{
-    {"-spin", ".spinSpeed", XrmoptionSepArg, 0},
-    {"-trans", ".translateSpeed", XrmoptionSepArg, 0},
-    {"-speed", ".juggleSpeed", XrmoptionSepArg, 0},
-    {"-maxobjs", ".maxObjs", XrmoptionSepArg, 0},
-    {"-minobjs", ".minObjs", XrmoptionSepArg, 0},
-    {"-maxhinc", ".maxHinc", XrmoptionSepArg, 0},
-    {"-minhinc", ".minHinc", XrmoptionSepArg, 0},
-};
+  glClear(GL_COLOR_BUFFER_BIT);
+}
 
 
-static argtype vars[] = 
+ENTRYPOINT void
+init_juggle (ModeInfo * mi)
 {
-    {&MaxObjects, "maxObjs", "MaxObjs", DEF_MAX_OBJS, t_Int},
-    {&MinObjects, "minObjs", "MinObjs", DEF_MIN_OBJS, t_Int},
-    {&MaxHeightInc, "maxHinc", "MaxHinc", DEF_MAX_HINC, t_Int},
-    {&MinHeightInc, "minHinc", "MinHinc", DEF_MIN_HINC, t_Int},
-    {&JuggleSpeed, "juggleSpeed", "JuggleSpeed", DEF_JUGGLE_SPEED, t_Float},
-    {&TranslateSpeed, "translateSpeed", "TranslateSpeed", DEF_TRANSLATE_SPEED, t_Float},
-    {&SpinSpeed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
-};
-
+  jugglestruct *sp = 0;
+  int wire = MI_IS_WIREFRAME(mi);
+
+  if (!juggles) {
+    juggles = (jugglestruct *)
+      calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct));
+    if (!juggles) {
+      fprintf(stderr, "%s: out of memory\n", progname);
+      exit(1);
+    }
+  }
 
-ENTRYPOINT ModeSpecOpt juggler3d_opts = {countof(opts), opts, countof(vars), vars};
+  sp = &juggles[MI_SCREEN(mi)];
 
+  sp->glx_context = init_GL(mi);
 
-ENTRYPOINT void reshape_juggler3d(ModeInfo *mi, int width, int height)
-{
-    JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
-    ResizeGL(&pConfig->RenderState, width, height);
-}
+  load_font (mi->dpy, "titleFont",  &sp->mode_font, &sp->font_dlist);
 
+  reshape_juggle (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
 
-ENTRYPOINT void init_juggler3d(ModeInfo* mi)
-{
-    JUGGLER3D_CONFIG* pConfig;
-    
-    if (pConfigInfo == NULL)
+  if (!wire)
     {
-        /* Apply suitable bounds checks to the input parameters */
-        MaxObjects = max(3, min(MaxObjects, 36));
-        MinObjects = max(3, min(MinObjects, MaxObjects));
-
-        MaxHeightInc = max(1, min(MaxHeightInc, 32));
-        MinHeightInc = max(1, min(MinHeightInc, MaxHeightInc));
-            
-        pConfigInfo = (JUGGLER3D_CONFIG*) calloc(
-            MI_NUM_SCREENS(mi), sizeof(JUGGLER3D_CONFIG));
-        if (pConfigInfo == NULL)
-        {
-            fprintf(stderr, "%s: out of memory\n", progname);
-            exit(1);
-        }
+      GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
+      GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+      GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+      GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
+
+      glEnable(GL_LIGHTING);
+      glEnable(GL_LIGHT0);
+      glEnable(GL_DEPTH_TEST);
+      glEnable(GL_CULL_FACE);
+
+      glLightfv(GL_LIGHT0, GL_POSITION, pos);
+      glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
+      glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
+      glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
     }
-    
-    pConfig = &pConfigInfo[MI_SCREEN(mi)];
-    pConfig->glxContext = init_GL(mi);
-    pConfig->CurrentFrameRate = 0.0f;
-    pConfig->FramesSinceSync = 0;
-    pConfig->LastSyncTime = 0;
-    InitGLSettings(&pConfig->RenderState, MI_IS_WIREFRAME(mi));
-
-    UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
-        MinHeightInc, MaxHeightInc);
-    
-    reshape_juggler3d(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
-}
-
 
-ENTRYPOINT void draw_juggler3d(ModeInfo* mi)
-{
-    JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
-    Display* pDisplay = MI_DISPLAY(mi);
-    Window hwnd = MI_WINDOW(mi);
-
-    if (pConfig->glxContext == NULL)
-        return;
-
-    glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pConfig->glxContext));
-    
-    /* While drawing, keep track of the rendering speed so we can adjust the
-     * animation speed so things appear consistent.  The basis of the this
-     * code comes from the frame rate counter (fps.c) but has been modified
-     * so that it reports the initial frame rate earlier (after 0.02 secs
-     * instead of 1 sec). */
-    
-    if (pConfig->FramesSinceSync >=  1 * (int) pConfig->CurrentFrameRate)
-    {
-        struct timeval tvnow;
-        unsigned now;
-            
-        # ifdef GETTIMEOFDAY_TWO_ARGS
-            struct timezone tzp;
-            gettimeofday(&tvnow, &tzp);
-        # else
-            gettimeofday(&tvnow);
-        # endif
-        
-        now = (unsigned) (tvnow.tv_sec * 1000000 + tvnow.tv_usec);
-        if (pConfig->FramesSinceSync == 0)
-        {
-            pConfig->LastSyncTime = now;
-        }
-        else
-        {
-            unsigned Delta = now - pConfig->LastSyncTime;
-            if (Delta > 20000)
-            {
-                pConfig->LastSyncTime = now;
-                pConfig->CurrentFrameRate = 
-                    (pConfig->FramesSinceSync * 1.0e6f) / Delta;
-                pConfig->FramesSinceSync = 0;
-            }
-        }
-    }
-    
-    pConfig->FramesSinceSync++;
-    
-    if (pConfig->RenderState.Time > 150.0f)
-    {
-        UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
-            MinHeightInc, MaxHeightInc);
-    }
-    DrawGLScene(&pConfig->RenderState);
-    
-    if (pConfig->CurrentFrameRate > 1.0e-6f)
-    {
-        pConfig->RenderState.Time += JuggleSpeed / pConfig->CurrentFrameRate;
-        pConfig->RenderState.SpinAngle += SpinSpeed / pConfig->CurrentFrameRate;
-        pConfig->RenderState.TranslateAngle += 
-            TranslateSpeed / pConfig->CurrentFrameRate;
+  make_random_colormap (0, 0, 0,
+                        mi->colors, &MI_NPIXELS(mi),
+                        True, False, 0, False);
+
+  {
+    double spin_speed   = 0.05;
+    double wander_speed = 0.001;
+    double spin_accel   = 0.05;
+    sp->rot = make_rotator (0, spin_speed, 0, 
+                            spin_accel, wander_speed, False);
+    sp->trackball = gltrackball_init ();
+  }
+
+  if (only && *only && strcmp(only, " ")) {
+    balls = clubs = torches = knives = rings = bballs = False;
+    if (!strcasecmp (only, "balls"))   balls   = True;
+    else if (!strcasecmp (only, "clubs"))   clubs   = True;
+    else if (!strcasecmp (only, "torches")) torches = True;
+    else if (!strcasecmp (only, "knives"))  knives  = True;
+    else if (!strcasecmp (only, "rings"))   rings   = True;
+    else if (!strcasecmp (only, "bballs"))  bballs  = True;
+    else {
+      (void) fprintf (stderr,
+               "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
+               "\t rings, or bballs (not \"%s\")\n", only);
+#ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
+      exit (1);
+#endif
     }
-    
-    if (mi->fps_p)
-        do_fps(mi);
-  
-    glFinish();
-    glXSwapBuffers(pDisplay, hwnd);
-}
+  }
+
+  /* #### hard to make this look good in OpenGL... */
+  torches = False;
+
+
+  if (sp->head == 0) {  /* first time initializing this juggler */
+
+       sp->count = ABS(MI_COUNT(mi));
+       if (sp->count == 0)
+         sp->count = 200;
+
+       /* record start time */
+       sp->begintime = time(NULL);
+       if(sp->patternindex.maxballs > 0) {
+         sp->num_balls = sp->patternindex.minballs +
+               NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
+       }
+
+        mi->polygon_count +=
+          show_figure(mi, True); /* Draw figure.  Also discovers
+                                    information about the juggler's
+                                    proportions */
+
+       /* "7" should be about three times the height of the juggler's
+          shoulders */
+       sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
+                                         7 * THROW_CATCH_INTERVAL);
+
+       if(!balls && !clubs && !torches && !knives && !rings && !bballs)
+         balls = True; /* Have to juggle something! */
+
+       /* create circular trajectory list */
+       ADD_ELEMENT(Trajectory, sp->head, sp->head);
+       if(sp->head == NULL){
+         free_juggle(sp);
+         return;
+       }
+
+       /* create circular object list */
+       ADD_ELEMENT(Object, sp->objects, sp->objects);
+       if(sp->objects == NULL){
+         free_juggle(sp);
+         return;
+       }
+
+       sp->pattern =  strdup(""); /* Initialise saved pattern with 
+                                      free-able memory */
+  }
+
+  sp = &juggles[MI_SCREEN(mi)];
+
+  if (pattern &&
+      (!*pattern ||
+       !strcasecmp (pattern, ".") ||
+       !strcasecmp (pattern, "random")))
+       pattern = NULL;
+
+  if (pattern == NULL && sp->patternindex.maxballs == 0) {
+       /* pattern list needs indexing */
+       int nelements = countof(portfolio);
+       int numpat = 0;
+       int i;
+
+       /* sort according to number of balls */
+       qsort((void*)portfolio, nelements,
+                 sizeof(portfolio[1]), compare_num_balls);
+
+       /* last pattern has most balls */
+       sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
+       /* run through sorted list, indexing start of each group
+          and number in group */
+       sp->patternindex.maxballs = 1;
+       for (i = 0; i < nelements; i++) {
+         int b = get_num_balls(portfolio[i].pattern);
+         if (b > sp->patternindex.maxballs) {
+               sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
+               if(numpat == 0) sp->patternindex.minballs = b;
+               sp->patternindex.maxballs = b;
+               numpat = 1;
+               sp->patternindex.index[sp->patternindex.maxballs].start = i;
+         } else {
+               numpat++;
+         }
+       }
+       sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
+  }
+
+  /* Set up programme */
+  change_juggle(mi);
+
+  /* Only put things here that won't interrupt the programme during
+        a window resize */
+
+  /* Use MIN so that users can resize in interesting ways, eg
+        narrow windows for tall patterns, etc */
+  sp->scale = MIN(SCENE_HEIGHT/480.0, SCENE_WIDTH/160.0);
 
+}
 
-ENTRYPOINT Bool juggler3d_handle_event(ModeInfo* mi, XEvent* pEvent)
+ENTRYPOINT Bool
+juggle_handle_event (ModeInfo *mi, XEvent *event)
 {
-  JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
-  RENDER_STATE* pState = &pConfig->RenderState;
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
 
-    if (pEvent->xany.type == ButtonPress &&
-        pEvent->xbutton.button == Button1)
+  if (event->xany.type == ButtonPress &&
+      event->xbutton.button == Button1)
     {
-      pState->button_down_p = True;
-      gltrackball_start (pState->trackball,
-                         pEvent->xbutton.x, pEvent->xbutton.y,
+      sp->button_down_p = True;
+      gltrackball_start (sp->trackball,
+                         event->xbutton.x, event->xbutton.y,
                          MI_WIDTH (mi), MI_HEIGHT (mi));
       return True;
     }
-    else if (pEvent->xany.type == ButtonRelease &&
-             pEvent->xbutton.button == Button1)
+  else if (event->xany.type == ButtonRelease &&
+           event->xbutton.button == Button1)
     {
-      pState->button_down_p = False;
+      sp->button_down_p = False;
       return True;
     }
-    else if (pEvent->xany.type == ButtonPress &&
-             (pEvent->xbutton.button == Button4 ||
-              pEvent->xbutton.button == Button5 ||
-              pEvent->xbutton.button == Button6 ||
-              pEvent->xbutton.button == Button7))
+  else if (event->xany.type == ButtonPress &&
+           (event->xbutton.button == Button4 ||
+            event->xbutton.button == Button5 ||
+            event->xbutton.button == Button6 ||
+            event->xbutton.button == Button7))
     {
-      gltrackball_mousewheel (pState->trackball, pEvent->xbutton.button, 2,
-                              !pEvent->xbutton.state);
+      gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
+                              !!event->xbutton.state);
       return True;
     }
-    else if (pEvent->xany.type == MotionNotify &&
-             pState->button_down_p)
+  else if (event->xany.type == MotionNotify &&
+           sp->button_down_p)
     {
-      gltrackball_track (pState->trackball,
-                         pEvent->xmotion.x, pEvent->xmotion.y,
+      gltrackball_track (sp->trackball,
+                         event->xmotion.x, event->xmotion.y,
                          MI_WIDTH (mi), MI_HEIGHT (mi));
       return True;
     }
-    else if (pEvent->xany.type == KeyPress)
+  else if (event->xany.type == KeyPress)
     {
-        char str[20];
-        KeySym Key = 0;
-        int count = XLookupString(&pEvent->xkey, str, 20, &Key, 0);
-        str[count] = '\0';
-        if (*str == ' ')
+      KeySym keysym;
+      char c = 0;
+      XLookupString (&event->xkey, &c, 1, &keysym, 0);
+      if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
         {
-            UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
-                MinHeightInc, MaxHeightInc);
+          change_juggle (mi);
+          return True;
         }
     }
-    
-    return False;
+
+
+  return False;
 }
 
-XSCREENSAVER_MODULE ("Juggler3D", juggler3d)
 
+ENTRYPOINT void
+draw_juggle (ModeInfo *mi)
+{
+  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
+  Display *dpy = MI_DISPLAY(mi);
+  Window window = MI_WINDOW(mi);
+
+  Trajectory *traj = NULL;
+  Object *o = NULL;
+  unsigned long future = 0;
+  char *pattern = NULL;
+
+  if (!sp->glx_context)
+    return;
+
+  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
+
+  glShadeModel(GL_SMOOTH);
+
+  glEnable(GL_DEPTH_TEST);
+  glEnable(GL_NORMALIZE);
+  glEnable(GL_CULL_FACE);
+
+  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+  glPushMatrix ();
+
+  glTranslatef(0,-3,0);
+
+  {
+    double x, y, z;
+    get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
+    glTranslatef((x - 0.5) * 8,
+                 (y - 0.5) * 3,
+                 (z - 0.5) * 15);
+
+    gltrackball_rotate (sp->trackball);
+
+    get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
+
+    if (y < 0.8) y = 0.8 - (y - 0.8);  /* always face forward */
+    if (y > 1.2) y = 1.2 - (y - 1.2);
+
+    glRotatef (x * 360, 1.0, 0.0, 0.0);
+    glRotatef (y * 360, 0.0, 1.0, 0.0);
+    glRotatef (z * 360, 0.0, 0.0, 1.0);
+  }
+
+  {
+    GLfloat scale = 20.0 / SCENE_HEIGHT;
+    glScalef(scale, scale, scale);
+  }
+
+  glRotatef (180, 0, 0, 1);
+  glTranslatef(-SCENE_WIDTH/2, -SCENE_HEIGHT/2, 0);
+  glTranslatef(0, -150, 0);
+
+  mi->polygon_count = 0;
+
+  /* Update timer */
+  if (real) {
+       struct timeval tv;
+       (void)gettimeofday(&tv, NULL);
+       sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
+  } else {
+       sp->time += MI_DELAY(mi) / 1000;
+  }
+
+  /* First pass: Move arms and strip out expired elements */
+  for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
+       if (traj->status != PREDICTOR) {
+         /* Skip any elements that need further processing */
+         /* We could remove them, but there shoudn't be many and they
+                would be needed if we ever got the pattern refiller
+                working */
+         continue;
+       }
+       if (traj->start > future) { /* Lookahead to the end of the show */
+         future = traj->start;
+       }
+       if (sp->time < traj->start) { /* early */
+         continue;
+       } else if (sp->time < traj->finish) { /* working */
+
+         /* Look for pattern name */
+         if(traj->pattern != NULL) {
+               pattern=traj->pattern;
+         }
+
+         if (traj->type == Empty || traj->type == Full) {
+               /* Only interested in hands on this pass */
+/*             double angle = traj->angle + traj->spin * (sp->time - traj->start);*/
+               double xd = 0, yd = 0;
+               DXPoint p;
+
+               /* Find the catching offset */
+               if(traj->object != NULL) {
+#if 0
+                  /* #### not sure what this is doing, but I'm guessing
+                     that the use of PERSPEC means this isn't needed
+                     in the OpenGL version? -jwz
+                   */
+                 if(ObjectDefs[traj->object->type].handle > 0) {
+                       /* Handles Need to be oriented */
+                       xd = ObjectDefs[traj->object->type].handle *
+                         PERSPEC * sin(angle);
+                       yd = ObjectDefs[traj->object->type].handle *
+                         cos(angle);
+                 } else
+#endif
+                    {
+                       /* Balls are always caught at the bottom */
+                       xd = 0;
+                       yd = -4;
+                 }
+               }
+               p.x = (CUBIC(traj->xp, sp->time) - xd);
+               p.y = (CUBIC(traj->yp, sp->time) + yd);
+               reach_arm(mi, traj->hand, &p);
+
+               /* Store updated hand position */
+               traj->x = p.x + xd;
+               traj->y = p.y - yd;
+         }
+         if (traj->type == Ball || traj->type == Full) {
+               /* Only interested in objects on this pass */
+               double x, y;
+               Trace *s;
+
+               if(traj->type == Full) {
+                 /* Adjusted these in the first pass */
+                 x = traj->x;
+                 y = traj->y;
+               } else {
+                 x = CUBIC(traj->xp, sp->time);
+                 y = CUBIC(traj->yp, sp->time);
+               }
+
+               ADD_ELEMENT(Trace, s, traj->object->trace->prev);
+               s->x = x;
+               s->y = y;
+               s->angle = traj->angle + traj->spin * (sp->time - traj->start);
+               s->divisions = traj->divisions;
+               traj->object->tracelen++;
+               traj->object->active = True;
+         }
+       } else { /* expired */
+         Trajectory *n = traj;
+         traj=traj->prev;
+         trajectory_destroy(n);
+       }
+  }
+
+
+  mi->polygon_count += show_figure(mi, False);
+  mi->polygon_count += show_arms(mi);
+
+  /* Draw Objects */
+  glTranslatef(0, 0, ARMLENGTH);
+  for (o = sp->objects->next; o != sp->objects; o = o->next) {
+       if(o->active) {
+         mi->polygon_count += ObjectDefs[o->type].draw(mi, o->color, 
+                                                        o->trace->prev);
+         o->active = False;
+       }
+  }
+
+
+  /* Save pattern name so we can erase it when it changes */
+  if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
+       free(sp->pattern);
+       sp->pattern = strdup(pattern);
+
+       if (MI_IS_VERBOSE(mi)) {
+         (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
+                                        MI_SCREEN(mi), sp->pattern);
+       }
+  }
+
+  if(sp->mode_font != None) {
+    print_gl_string (mi->dpy, sp->mode_font, sp->font_dlist,
+                     mi->xgwa.width, mi->xgwa.height,
+                     10, mi->xgwa.height - 10,
+                     sp->pattern, False);
+  }
+
+#ifdef MEMTEST
+  if((int)(sp->time/10) % 1000 == 0)
+       (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
 #endif
+
+  if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
+       refill_juggle(mi);
+  } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
+       release_juggle(mi);
+       init_juggle(mi);
+  }
+
+  glPopMatrix ();
+
+  if (mi->fps_p) do_fps (mi);
+  glFinish();
+
+  glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle)
+
+#endif /* USE_GL */