/* juggle, Copyright (c) 1996-2009 Tim Auckland <tda10.geo@yahoo.com>
 * and Jamie Zawinski <jwz@jwz.org>
 *
 * 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
 */

/*-
 * 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
 *
 * 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.
 *
 * To go from a-notation to s-notation, you start by mapping each a_n
 * to a permutation of N, the natural numbers.
 *
 * 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.
 *
 * 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.
 *
 * 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:
 *
 * (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
 *
 * ...55555566771771771771771...
 *
 * So the answer is to do two 6 throws and then go straight into
 * (771).  Coming back down of course,
 *
 * ...5515515515515515555555555...
 *
 * converts to
 *
 * ...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'.]
 */

/* This code uses so many linked lists it's worth having a built-in
 * leak-checker */
#undef MEMTEST

#ifdef VMS
#include "vms_x_fix.h"
#include <X11/Intrinsic.h>
#endif

#ifdef STANDALONE
#define MODE_juggler3d
#define PROGCLASS       "Juggler3D"
#define HACK_INIT       init_juggler3d
#define HACK_DRAW       draw_juggler3d
#define HACK_RESHAPE    reshape_juggler3d
#define juggler3d_opts     xlockmore_opts
# 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"
#include "xlockmore.h"
#define refresh_juggler3d 0

#define MI_COLOR_red    ((MI)->colors->red)
#define MI_COLOR_green    ((MI)->colors->green)
#define MI_COLOR_blue    ((MI)->colors->blue)

/* TODO for JWZ: move this to better place */
#define MI_POLYGONCOUNT(MI)    ((MI)->polygon_count)

#else /* !STANDALONE */
#include "xlock.h"              /* from the xlockmore distribution */
#include "visgl.h"
#define ENTRYPOINT
#define MI_COLOR_red    (NRAND(50000)+15536)
#define MI_COLOR_green    (NRAND(50000)+15536)
#define MI_COLOR_blue    (NRAND(50000)+15536)
#endif /* !STANDALONE */

#ifdef MODE_juggler3d

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

#include "sphere.h"
#include "tube.h"
#include "rotator.h"
#include "gltrackball.h"
#include "glxfonts.h"
#include <ctype.h>

#ifdef USE_GL /* whole file */
#include <GL/glu.h>

#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 OptionStruct desc[] =
{
  { "-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 juggler3d_opts =
 {countof(opts), opts, countof(vars), vars, desc};

#ifdef USE_MODULES
ModStruct   juggler3d_description =
{"juggler3d", "init_juggler3d", "draw_juggler3d", "release_juggler3d",
 "draw_juggler3d", "change_juggler3d", (char *) NULL, &juggler3d_opts,
 10000, 200, 1000, 1, 64, 1.0, "",
 "Shows a 3D Juggler juggling", 0, NULL};

#endif


/* 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,
  },
};

/**************************
 * 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_j;

/* 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_j *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"},

};



typedef struct { int start; int number; } PatternIndex;

struct patternindex {
  int minballs;
  int maxballs;
  PatternIndex index[countof(portfolio)];
};


/* Jugglestruct: per-screen global data.  The master Object
 * and Trajectory lists are anchored here. */
typedef struct {
#ifdef WIN32
  HGLRC         glx_context;
#else
  GLXContext   *glx_context;
#endif
  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_j       *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); \
}

/* 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_j* o)
{
  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 */
}

static void
free_juggler3d(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_j*)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 Bool
add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
{
  Trajectory *t;

  ADD_ELEMENT(Trajectory, t, sp->head->prev);
  if(t == NULL){ /* Out of Memory */
	free_juggler3d(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;
}

/* add a Thratch to the performance */
static Bool
program(ModeInfo *mi, const char *patn, const char *name, int cycles)
{
  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;
}

/*
 ~~~~\~~~~~\~~~
 \\~\\~\~\\\~~~
 \\~\\\\~\\\~\~
 \\\\\\\\\\\~\\

[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]

4 4 1 3 12 2 4 1 4 4 13 0 3 1

*/
#define BOUNCEOVER 10
#define KICKMIN 7
#define THROWMAX 20

/* 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;
	}
  }
}

/* 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. */

/* ..n1.. -> .. LTn RT1 LC RC .. */
/* ..nm.. -> .. LTn LC RTm RC .. */

static Bool
part(jugglestruct *sp)
{
  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_juggler3d(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;
}

/* Connnect up throws and catches to figure out which ball goes where.
   Do the same with the juggler's hands. */

static void
lob(ModeInfo *mi)
{
  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_j, 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;
	}
  }
}

/* Clap when both hands are empty */
static void
clap(jugglestruct *sp)
{
  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)

/* 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)
{
  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;
}

/* 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 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;
}

/* 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;
}




#define SX 25 /* Shoulder Width */

/* 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

	  t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
	  t->y = yo;

	  /* Only mark complete if it was already linked */
	  if(t->status == LINKEDACTION) {
		t->status = PTHRATCH;
	  }
	}
  }
}


/* Private physics functions */

/* Compute the spin-rate for a trajectory.  Different types of throw
   (eg, regular thows, bounces, kicks, etc) have different spin
   requirements.

   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

  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;
  }
}


/* compute the angle at the end of a spinning trajectory */
static double
end_spin(Trajectory *t)
{
  return t->angle + t->spin * (t->finish - t->start);
}

/* 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)
{
  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
	}
  }
}

static double
find_bounce(jugglestruct *sp,
			double yo, double yf, double yc, double tc, double cor)
{
  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 Trajectory*
new_predictor(const Trajectory *t, int start, int finish, double angle)
{
  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;
}

/* 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;
}

/* Turn abstract hand motions into cubic splines. */
static void
hands(jugglestruct *sp)
{
  Trajectory *t, *u, *v;

  for (t = sp->head->next; t != sp->head; t = t->next) {
	/* no throw => no velocity */
	if (t->status != BPREDICTOR) {
	  continue;
	}

	u = t->handlink;
	if (u == NULL) { /* no next catch */
	  continue;
	}
	v = u->handlink;
	if (v == NULL) { /* no next throw */
	  continue;
	}

	/* double spline takes hand from throw, thru catch, to
	   next throw */

	t->finish = u->start;
	t->status = PREDICTOR;

	u->finish = v->start;
	u->status = PREDICTOR;


	/* FIXME: These adjustments leave a small glitch when alternating
	   balls and clubs.  Just hope no-one notices.  :-) */

	/* make sure empty hand spin matches the thrown object in case it
	   had a handle */

	t->spin = ((t->hand == LEFT)? -1 : 1 ) *
	  fabs((u->angle - t->angle)/(u->start - t->start));

	u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
	  fabs((v->angle - u->angle)/(v->start - u->start));

	(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);

	t->status = PREDICTOR;
  }
}

/* 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)
{
  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;
  }
}


/* NOTE: returned x, y adjusted for arm reach */
static void
reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
{
  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;
}

#if DEBUG
/* dumps a human-readable rendition of the current state of the juggle
   pipeline to stderr for debugging */
static void
dump(jugglestruct *sp)
{
  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 int get_num_balls(const char *j)
{
  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;
}

static int
compare_num_balls(const void *p1, const void *p2)
{
  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);
  }
}


/**************************************************************************
 *                        Rendering Functions                             *
 *                                                                        *
 **************************************************************************/

static int
show_arms(ModeInfo * mi)
{
  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();

        /* Hand */
        glPushMatrix();
        glTranslatef (a[0].x, a[0].y, ARMLENGTH);
        glScalef(8, 8, 8);
        polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
        glPopMatrix();

  }
  return polys;
}

static int
show_figure(ModeInfo * mi, Bool init)
{
  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;

    glPushMatrix();
    {
      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);
        
        /* 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();

        /* 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();

        /* 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();

        /* 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();

        /* 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();

        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;

/* lifted from sphere.c */
static int
striped_unit_sphere (int stacks, int slices, 
                     int stripes, 
                     GLfloat *color1, GLfloat *color2,
                     int wire_p)
{
  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;
    }

  glFrontFace(GL_CW);

  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;

      glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
                    ((j == 0 || j == stacks-1 ||
                     j % (stacks / (stripes+1)))
                     ? color1 : color2));

      glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
      for (i = 0; i <= slices; i++)
        {
          theta3 = i * (M_PI+M_PI) / slices;

          if (wire_p && i != 0)
            {
              glVertex3f (lb.x, lb.y, lb.z);
              glVertex3f (la.x, la.y, la.z);
            }

          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;
}



static int
show_ball(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);
  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_COLOR_red   / 65536.0;
  gcolor1[1] = MI_COLOR_green / 65536.0;
  gcolor1[2] = MI_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);

    glRotatef (s->angle / M_PI*180, 1, 1, 0);

    polys += striped_unit_sphere (slices, slices, s->divisions, 
                                  gcolor1, gcolor2, MI_IS_WIREFRAME(mi));
    glPopMatrix();
  }
  return polys;
}

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_COLOR_red   / 65536.0;
  gcolor1[1] = MI_COLOR_green / 65536.0;
  gcolor1[2] = MI_COLOR_blue  / 65536.0;

  {
    GLfloat scale = radius;
    glPushMatrix();
    glTranslatef(x, y, 0);
    glScalef(scale, scale, scale);

    glTranslatef (0, 0, 2);  /* put end of handle in hand */

    glRotatef (s->angle / M_PI*180, 1, 0, 0);

    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));

    glTranslatef (0, 2, 0);
    glScalef (0.25, 0.25, 0.25);
    polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));

    glPopMatrix();
  }
  return polys;
}


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;
}


static int
show_knife(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);
  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_COLOR_red   / 65536.0;
  gcolor1[1] = MI_COLOR_green / 65536.0;
  gcolor1[2] = MI_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;
}


static int
show_ring(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 = 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_COLOR_red   / 65536.0;
  gcolor1[1] = MI_COLOR_green / 65536.0;
  gcolor1[2] = MI_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();
    }

  /* 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();

  glFrontFace (GL_CCW);
  glPopMatrix();
  return polys;
}


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_COLOR_red   / 65536.0;
  gcolor1[1] = MI_COLOR_green / 65536.0;
  gcolor1[2] = MI_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();
    }

    glPopMatrix();
  }
  return polys;
}


/**************************************************************************
 *                    Public Functions                                    *
 *                                                                        *
 **************************************************************************/


ENTRYPOINT void
release_juggler3d (ModeInfo * mi)
{
  if (juggles != NULL) {
	int screen;

	for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
	  free_juggler3d(&juggles[screen]);
	free(juggles);
	juggles = (jugglestruct *) NULL;
  }
}

/* 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 */

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_juggler3d(sp);
	return;
  }

  hands(sp);
#ifdef DEBUG
  if(MI_IS_DEBUG(mi)) dump(sp);
#endif
}

#ifdef STANDALONE
static
#endif
void
change_juggler3d(ModeInfo * mi)
{
  jugglestruct *sp = NULL;
  Trajectory *t;

  if (juggles == NULL)
	return;
  sp = &juggles[MI_SCREEN(mi)];

  /* 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);
	}
  }

  /* Pick the current object theme */
  sp->objtypes = choose_object();

  refill_juggle(mi);

  MI_POLYGONCOUNT(mi) += show_figure(mi, True);
}


ENTRYPOINT void
reshape_juggler3d (ModeInfo *mi, int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;

  glViewport (0, 0, (GLint) width, (GLint) height);

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective (30.0, 1/h, 1.0, 100.0);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt( 0.0, 0.0, 30.0,
             0.0, 0.0, 0.0,
             0.0, 1.0, 0.0);

  glClear(GL_COLOR_BUFFER_BIT);
}


ENTRYPOINT void
init_juggler3d (ModeInfo * mi)
{
  jugglestruct *sp = 0;
  int wire = MI_IS_WIREFRAME(mi);

  if (!juggles) {
    juggles = (jugglestruct *)
      calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct));
#ifdef STANDALONE
    if (!juggles) {
      fprintf(stderr, "%s: out of memory\n", progname);
      exit(1);
    }
#endif
  }

  sp = &juggles[MI_SCREEN(mi)];
  sp->glx_context = init_GL(mi);

  load_font (MI_DISPLAY(mi), (char *) "titleFont",  &sp->mode_font, &sp->font_dlist);

  reshape_juggler3d (mi, MI_WIDTH(mi), MI_HEIGHT(mi));

  if (!wire)
    {
      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);
    }
#ifdef STANDALONE
  make_random_colormap (0, 0, 0,
                        mi->colors, &MI_NPIXELS(mi),
                        True, False, 0, False);
#endif
  {
    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
    }
  }

  /* #### 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_POLYGONCOUNT(mi) +=
          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_juggler3d(sp);
	  return;
	}

	/* create circular object list */
	ADD_ELEMENT(Object_j, sp->objects, sp->objects);
	if(sp->objects == NULL){
	  free_juggler3d(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_juggler3d(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
juggle_handle_event (ModeInfo *mi, XEvent *event)
{
  jugglestruct *sp = &juggles[MI_SCREEN(mi)];

  if (event->xany.type == ButtonPress &&
      event->xbutton.button == Button1)
    {
      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 (event->xany.type == ButtonRelease &&
           event->xbutton.button == Button1)
    {
      sp->button_down_p = False;
      return True;
    }
  else if (event->xany.type == ButtonPress &&
           (event->xbutton.button == Button4 ||
            event->xbutton.button == Button5
#ifdef STANDALONE
				||
            event->xbutton.button == Button6 ||
            event->xbutton.button == Button7
#endif
		))
    {
      gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
                              !!event->xbutton.state);
      return True;
    }
  else if (event->xany.type == MotionNotify &&
           sp->button_down_p)
    {
      gltrackball_track (sp->trackball,
                         event->xmotion.x, event->xmotion.y,
                         MI_WIDTH (mi), MI_HEIGHT (mi));
      return True;
    }
  else if (event->xany.type == KeyPress)
    {
      KeySym keysym;
      char c = 0;
      XLookupString (&event->xkey, &c, 1, &keysym, 0);
      if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
        {
          change_juggler3d (mi);
          return True;
        }
    }


  return False;
}


ENTRYPOINT void
draw_juggler3d (ModeInfo *mi)
{
  jugglestruct *sp = &juggles[MI_SCREEN(mi)];
  Display *dpy = MI_DISPLAY(mi);
  Window window = MI_WINDOW(mi);

  Trajectory *traj = NULL;
  Object_j *o = NULL;
  unsigned long future = 0;
  char *pattern = NULL;

  if (!sp->glx_context)
    return;
#ifdef WIN32
   wglMakeCurrent(hdc, sp->glx_context);
#else
  glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
#endif
  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_POLYGONCOUNT(mi) = 0;

  /* Update timer */
#ifndef WIN32
  if (real) {
	struct timeval tv;
	(void)gettimeofday(&tv, NULL);
	sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
  } else
#endif
  {
	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_POLYGONCOUNT(mi) += show_figure(mi, False);
  MI_POLYGONCOUNT(mi) += show_arms(mi);

  /* Draw Objects */
  glTranslatef(0, 0, ARMLENGTH);
  for (o = sp->objects->next; o != sp->objects; o = o->next) {
	if(o->active) {
          MI_POLYGONCOUNT(mi) += 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_DISPLAY(mi), sp->mode_font, sp->font_dlist,
                     MI_WIDTH(mi), MI_HEIGHT(mi),
                     10, MI_HEIGHT(mi) - 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);
  }  if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
	release_juggler3d(mi);
	init_juggler3d(mi);
  }

  glPopMatrix ();

  if (MI_IS_FPS(mi)) do_fps (mi);
  glFinish();

  glXSwapBuffers(dpy, window);
}

#if STANDALONE
XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle)
#endif

#endif /* USE_GL */
#endif /* MODE_juggler3d */
