7634d3e5f11720a62c15e29ee7c5f3969d17ba67
[xscreensaver] / hacks / juggle.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* juggle */
3
4 #if !defined( lint ) && !defined( SABER )
5 static const char sccsid[] = "@(#)juggle.c      5.10 2003/09/02 xlockmore";
6
7 #endif
8
9 /*-
10  * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
11  *
12  * Permission to use, copy, modify, and distribute this software and its
13  * documentation for any purpose and without fee is hereby granted,
14  * provided that the above copyright notice appear in all copies and that
15  * both that copyright notice and this permission notice appear in
16  * supporting documentation.
17  *
18  * This file is provided AS IS with no warranties of any kind.  The author
19  * shall have no liability with respect to the infringement of copyrights,
20  * trade secrets or any patents by this file or any part thereof.  In no
21  * event will the author be liable for any lost revenue or profits or
22  * other special, indirect and consequential damages.
23  *
24  * Revision History
25  * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
26  *              Add -rings, -bballs.  Add -describe.  Finally made
27  *              live pattern updates possible.  Add refill_juggle(),
28  *              change_juggle() and reshape_juggle().  Make
29  *              init_juggle() non-destructive.  Reorder erase/draw
30  *              operations.  Update xscreensaver xml and manpage.
31  * 15-Nov-2004: [TDA] Fix all memory leaks.
32  * 12-Nov-2004: [TDA] Add -torches and another new trail
33  *              implementation, so that different objects can have
34  *              different length trails.
35  * 11-Nov-2004: [TDA] Clap when all the balls are in the air.
36  * 10-Nov-2004: [TDA] Display pattern name converted to hight
37  *              notation.
38  * 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
39  * 02-Sep-2003: Non-real time to see what is happening without a
40  *              strobe effect for slow machines.
41  * 01-Nov-2000: Allocation checks
42  * 1996: Written
43  */
44
45 /*-
46  * TODO
47  * Implement the anonymously promised -uni option.
48  */
49
50
51 /*
52  * Notes on Adam Chalcraft Juggling Notation (used by permission)
53  * a-> Adam's notation  s-> Site swap (Cambridge) notation
54  *
55  * To define a map from a-notation to s-notation ("site-swap"), both
56  * of which look like doubly infinite sequences of natural numbers. In
57  * s-notation, there is a restriction on what is allowed, namely for
58  * the sequence s_n, the associated function f(n)=n+s_n must be a
59  * bijection. In a-notation, there is no restriction.
60  *
61  * To go from a-notation to s-notation, you start by mapping each a_n
62  * to a permutation of N, the natural numbers.
63  *
64  * 0 -> the identity
65  * 1 -> (10) [i.e. f(1)=0, f(0)=1]
66  * 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
67  * 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
68  * etc.
69  *
70  * Then for each n, you look at how long 0 takes to get back to 0
71  * again and you call this t_n. If a_n=0, for example, then since the
72  * identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
73  * a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
74  * next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
75  * following the 1. Finally, set s_n = t_n - 1.
76  *
77  * To give some examples, it helps to have a notation for cyclic
78  * sequences. By (123), for example, I mean ...123123123123... . Now
79  * under the a-notation -> s-notation mapping we have some familiar
80  * examples:
81  *
82  * (0)->(0), (1)->(1), (2)->(2) etc.
83  * (21)->(31), (31)->(51), (41)->(71) etc.
84  * (10)->(20), (20)->(40), (30)->(60) etc.
85  * (331)->(441), (312)->(612), (303)->(504), (321)->(531)
86  * (43)->(53), (434)->(534), (433)->(633)
87  * (552)->(672)
88  *
89  * In general, the number of balls is the *average* of the s-notation,
90  * and the *maximum* of the a-notation. Another theorem is that the
91  * minimum values in the a-notation and the s-notation and equal, and
92  * preserved in the same positions.
93  *
94  * The usefulness of a-notation is the fact that there are no
95  * restrictions on what is allowed. This makes random juggle
96  * generation much easier. It also makes enumeration very
97  * easy. Another handy feature is computing changes.  Suppose you can
98  * do (5) and want a neat change up to (771) in s-notation [Mike Day
99  * actually needed this example!]. Write them both in a-notation,
100  * which gives (5) and (551). Now concatenate them (in general, there
101  * may be more than one way to do this, but not in this example), to
102  * get
103  *
104  * ...55555555551551551551551...
105  *
106  * Now convert back to s-notation, to get
107  *
108  * ...55555566771771771771771...
109  *
110  * So the answer is to do two 6 throws and then go straight into
111  * (771).  Coming back down of course,
112  *
113  * ...5515515515515515555555555...
114  *
115  * converts to
116  *
117  * ...7717717717716615555555555...
118  *
119  * so the answer is to do a single 661 and then drop straight down to
120  * (5).
121  *
122  * [The number of balls in the generated pattern occasionally changes.
123  * In order to decrease the number of balls I had to introduce a new
124  * symbol into the Adam notation, [*] which means 'lose the current
125  * ball'.]
126  */
127
128 /* This code uses so many linked lists it's worth having a built-in
129  * leak-checker */
130 #undef MEMTEST
131
132 #ifdef STANDALONE
133 # define MODE_juggle
134 # define DEFAULTS       "*delay:   10000 \n" \
135                                         "*count:   200   \n" \
136                                         "*cycles:  1000  \n" \
137                                         "*ncolors: 32    \n" \
138                                         "*font:    -*-times-bold-r-normal-*-180-*\n"
139 # define refresh_juggle 0
140 # define juggle_handle_event 0
141 # undef SMOOTH_COLORS
142 # include "xlockmore.h"         /* in xscreensaver distribution */
143 #else /* STANDALONE */
144 # include "xlock.h"             /* in xlockmore distribution */
145 #endif /* STANDALONE */
146
147 #ifdef MODE_juggle
148
149 #if 0
150 #define XClearWindow(d, w) \
151 { \
152  XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \
153  XFillRectangle(d, w, MI_GC(mi), \
154    0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \
155 }
156 #endif
157
158 #define DEF_PATTERN "random" /* All patterns */
159 #define DEF_TAIL "1" /* No trace */
160 #ifdef UNI
161 /* Maybe a ROLA BOLA would be at a better angle for viewing */
162 #define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
163 #endif
164 #define DEF_REAL "True"
165 #define DEF_DESCRIBE "True"
166
167 #define DEF_BALLS "True" /* Use Balls */
168 #define DEF_CLUBS "True" /* Use Clubs */
169 #define DEF_TORCHES "True" /* Use Torches */
170 #define DEF_KNIVES "True" /* Use Knives */
171 #define DEF_RINGS "True" /* Use Rings */
172 #define DEF_BBALLS "True" /* Use Bowling Balls */
173
174 #ifndef XtNumber
175 #define XtNumber(arr)   ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
176 #endif
177
178 static char *pattern;
179 static int tail;
180 #ifdef UNI
181 static Bool uni;
182 #endif
183 static Bool real;
184 static Bool describe;
185 static Bool balls;
186 static Bool clubs;
187 static Bool torches;
188 static Bool knives;
189 static Bool rings;
190 static Bool bballs;
191 static char *only;
192
193 static XrmOptionDescRec opts[] =
194 {
195   {"-pattern",  ".juggle.pattern",  XrmoptionSepArg, NULL  },
196   {"-tail",     ".juggle.tail",     XrmoptionSepArg, NULL  },
197 #ifdef UNI
198   {"-uni",      ".juggle.uni",      XrmoptionNoArg,  "on"  },
199   {"+uni",      ".juggle.uni",      XrmoptionNoArg,  "off" },
200 #endif
201   {"-real",     ".juggle.real",     XrmoptionNoArg,  "on"  },
202   {"+real",     ".juggle.real",     XrmoptionNoArg,  "off" },
203   {"-describe", ".juggle.describe", XrmoptionNoArg,  "on"  },
204   {"+describe", ".juggle.describe", XrmoptionNoArg,  "off" },
205   {"-balls",    ".juggle.balls",    XrmoptionNoArg,  "on"  },
206   {"+balls",    ".juggle.balls",    XrmoptionNoArg,  "off" },
207   {"-clubs",    ".juggle.clubs",    XrmoptionNoArg,  "on"  },
208   {"+clubs",    ".juggle.clubs",    XrmoptionNoArg,  "off" },
209   {"-torches",  ".juggle.torches",  XrmoptionNoArg,  "on"  },
210   {"+torches",  ".juggle.torches",  XrmoptionNoArg,  "off" },
211   {"-knives",   ".juggle.knives",   XrmoptionNoArg,  "on"  },
212   {"+knives",   ".juggle.knives",   XrmoptionNoArg,  "off" },
213   {"-rings",    ".juggle.rings",    XrmoptionNoArg,  "on"  },
214   {"+rings",    ".juggle.rings",    XrmoptionNoArg,  "off" },
215   {"-bballs",   ".juggle.bballs",   XrmoptionNoArg,  "on"  },
216   {"+bballs",   ".juggle.bballs",   XrmoptionNoArg,  "off" },
217   {"-only",     ".juggle.only",     XrmoptionSepArg, NULL  },
218 };
219 static argtype vars[] =
220 {
221   { &pattern,  "pattern",  "Pattern",  DEF_PATTERN,  t_String },
222   { &tail,     "tail",     "Tail",     DEF_TAIL,     t_Int    },
223 #ifdef UNI
224   { &uni,      "uni",      "Uni",      DEF_UNI,      t_Bool   },
225 #endif
226   { &real,     "real",     "Real",     DEF_REAL,     t_Bool   },
227   { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool   },
228   { &balls,    "balls",    "Clubs",    DEF_BALLS,    t_Bool   },
229   { &clubs,    "clubs",    "Clubs",    DEF_CLUBS,    t_Bool   },
230   { &torches,  "torches",  "Torches",  DEF_TORCHES,  t_Bool   },
231   { &knives,   "knives",   "Knives",   DEF_KNIVES,   t_Bool   },
232   { &rings,    "rings",    "Rings",    DEF_RINGS,    t_Bool   },
233   { &bballs,   "bballs",   "BBalls",   DEF_BBALLS,   t_Bool   },
234   { &only,     "only",     "BBalls",   " ",          t_String },
235 };
236 static OptionStruct desc[] =
237 {
238   { "-pattern string", "Cambridge Juggling Pattern" },
239   { "-tail num",       "Trace Juggling Patterns" },
240 #ifdef UNI
241   { "-/+uni",          "Unicycle" },
242 #endif
243   { "-/+real",         "Real-time" },
244   { "-/+describe",     "turn on/off pattern descriptions." },
245   { "-/+balls",        "turn on/off Balls." },
246   { "-/+clubs",        "turn on/off Clubs." },
247   { "-/+torches",      "turn on/off Flaming Torches." },
248   { "-/+knives",       "turn on/off Knives." },
249   { "-/+rings",        "turn on/off Rings." },
250   { "-/+bballs",       "turn on/off Bowling Balls." },
251   { "-only",           "Turn off all objects but the named one." },
252 };
253
254 ENTRYPOINT ModeSpecOpt juggle_opts =
255   { XtNumber(opts), opts, XtNumber(vars), vars, desc };
256
257 #ifdef USE_MODULES
258 ModStruct   juggle_description = {
259         "juggle", "init_juggle", "draw_juggle", "release_juggle",
260         "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
261         10000, 200, 1000, 1, 64, 1.0, "",
262         "Shows a Juggler, juggling", 0, NULL
263 };
264
265 #endif
266
267 #ifdef USE_XVMSUTILS
268 # include <X11/unix_time.h>
269 #endif
270
271 /* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480.  All
272    "thicknesses" are scaled by sqrt(sp->scale) so that they are
273    proportionally thicker for smaller windows.  Objects spinning out
274    of the plane (such as clubs) fake perspective by compressing their
275    horizontal coordinates by PERSPEC  */
276
277 /* Figure */
278 #define ARMLENGTH 50
279 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
280 #define POSE 10
281 #define BALLRADIUS ARMWIDTH
282
283 #define PERSPEC  0.4
284
285 /* macros */
286 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
287
288 /* Timing based on count.  Units are milliseconds.  Juggles per second
289        is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
290
291 #define THROW_CATCH_INTERVAL (sp->count)
292 #define THROW_NULL_INTERVAL  (sp->count * 0.5)
293 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
294
295 /********************************************************************
296  * Trace Definitions                                                *
297  *                                                                  *
298  * These record rendering data so that a drawn object can be erased *
299  * later.  Each object has its own Trace list.                      *
300  *                                                                  *
301  ********************************************************************/
302
303 typedef struct {double x, y; } DXPoint;
304 typedef struct trace *TracePtr;
305 typedef struct trace {
306   TracePtr next, prev;
307   double x, y;
308   double angle;
309   int divisions;
310   DXPoint dlast;
311 #ifdef MEMTEST
312   char pad[1024];
313 #endif
314 } Trace;
315
316 /*******************************************************************
317  * Object Definitions                                              *
318  *                                                                 *
319  * These describe the various types of Object that can be juggled  *
320  *                                                                 *
321  *******************************************************************/
322 typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
323
324 static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
325 static DrawProc show_ring, show_bball;
326
327 typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
328                           NUM_OBJECT_TYPES} ObjType;
329 #define OBJMIXPROB 20   /* inverse of the chances of using an odd
330                                                    object in the pattern */
331
332 static const struct {
333   DrawProc *draw;                           /* Object Rendering function */
334   int       handle;                         /* Length of object's handle */
335   int       mintrail;                            /* Minimum trail length */
336   double    cor;      /* Coefficient of Restitution.  perfect bounce = 1 */
337   double    weight;          /* Heavier objects don't get thrown as high */
338 } ObjectDefs[] = {
339   { /* Ball */
340         show_ball,
341         0,
342         1,
343         0.9,
344         1.0,
345   },
346   { /* Club */
347         show_europeanclub,
348         15,
349         1,
350         0.55, /* Clubs don't bounce too well */
351         1.0,
352   },
353   { /* Torch */
354         show_torch,
355         15,
356         20, /* Torches need flames */
357         0, /* Torches don't bounce -- fire risk! */
358         1.0,
359   },
360   { /* Knife */
361         show_knife,
362         15,
363         1,
364         0, /* Knives don't bounce */
365         1.0,
366   },
367   { /* Ring */
368         show_ring,
369         15,
370         1,
371         0.8,
372         1.0,
373   },
374   { /* Bowling Ball */
375         show_bball,
376         0,
377         1,
378         0.2,
379         5.0,
380   },
381 };
382
383 /**************************
384  * Trajectory definitions *
385  **************************/
386
387 typedef enum {HEIGHT, ADAM} Notation;
388 typedef enum {Empty, Full, Ball} Throwable;
389 typedef enum {LEFT, RIGHT} Hand;
390 typedef enum {THROW, CATCH} Action;
391 typedef enum {HAND, ELBOW, SHOULDER} Joint;
392 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
393                           PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
394 typedef struct {double a, b, c, d; } Spline;
395 typedef DXPoint Arm[3];
396
397 /* A Wander contains a Spline and a time interval.  A list of Wanders
398  * describes the performer's position as he moves around the screen.  */
399 typedef struct wander *WanderPtr;
400 typedef struct wander {
401   WanderPtr next, prev;
402   double x;
403   unsigned long finish;
404   Spline s;
405 #ifdef MEMTEST
406   char pad[1024];
407 #endif
408 } Wander;
409
410 /* Object is an arbitrary object being juggled.  Each Trajectory
411  * references an Object ("count" tracks this), and each Object is also
412  * linked into a global Objects list.  Objects may include a Trace
413  * list for tracking erasures. */
414 typedef struct object *ObjectPtr;
415 typedef struct object {
416   ObjectPtr next, prev;
417
418   ObjType type;
419   int     color;
420   int     count; /* reference count */
421   Bool    active; /* Object is in use */
422
423   Trace  *trace;
424   int     tracelen;
425   int     tail;
426 #ifdef MEMTEST
427   char pad[1024];
428 #endif
429 } Object;
430
431 /* Trajectory is a segment of juggling action.  A list of Trajectories
432  * defines the juggling performance.  The Trajectory list goes through
433  * multiple processing steps to convert it from basic juggling
434  * notation into rendering data. */
435
436 typedef struct trajectory *TrajectoryPtr;
437 typedef struct trajectory {
438   TrajectoryPtr prev, next;  /* for building list */
439   TrajectoryStatus status;
440
441   /* Throw */
442   char posn;
443   int height;
444   int adam;
445   char *pattern;
446   char *name;
447
448   /* Action */
449   Hand hand;
450   Action action;
451
452   /* LinkedAction */
453   int color;
454   Object *object;
455   int divisions;
456   double angle, spin;
457   TrajectoryPtr balllink;
458   TrajectoryPtr handlink;
459
460   /* PThratch */
461   double cx; /* Moving juggler */
462   double x, y; /* current position */
463   double dx, dy; /* initial velocity */
464
465   /* Predictor */
466   Throwable type;
467   unsigned long start, finish;
468   Spline xp, yp;
469
470 #ifdef MEMTEST
471   char pad[1024];
472 #endif
473 } Trajectory;
474
475
476 /*******************
477  * Pattern Library *
478  *******************/
479
480 typedef struct {
481   const char * pattern;
482   const char * name;
483 } patternstruct;
484
485 /* List of popular patterns, in any order */
486 /* Patterns should be given in Adam notation so the generator can
487    concatenate them safely.  Null descriptions are ok.  Height
488    notation will be displayed automatically.  */
489 /* Can't const this because it is qsorted.  This *should* be reentrant,
490    I think... */
491 static /*const*/ patternstruct portfolio[] = {
492   {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
493   {"[2 0]", /* 4 0 */ "2 in 1 hand"},
494   {"[2 0 1]", /* 5 0 1 */},
495   {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
496   {"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
497   {"[2 0 1 1]", /* 6 0 1 1 */},
498
499   {"[3]", /* 3 */ "3 cascade"},
500   {"[+3]", /* +3 */ "reverse 3 cascade"},
501   {"[=3]", /* =3 */ "cascade 3 under arm"},
502   {"[&3]", /* &3 */ "cascade 3 catching under arm"},
503   {"[_3]", /* _3 */ "bouncing 3 cascade"},
504   {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
505   {"[3 2 1]", /* 5 3 1" */},
506   {"[3 3 1]", /* 4 4 1" */},
507   {"[3 1 2]", /* 6 1 2 */ "See-saw"},
508   {"[=3 3 1 2]", /* =4 5 1 2 */},
509   {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
510   {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
511   {"[3 3 1]", /* 4 4 1 */},
512   {"[+3 2 3]", /* +4 2 3 */},
513   {"[+3 1]", /* +5 1 */ "3 shower"},
514   {"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
515   {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
516   {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
517   {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
518   {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
519   {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
520   {"[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 */},
521   {"[3 0 2 0]", /* 8 0 4 0 */},
522   {"[_3 2 1]", /* _5 3 1 */},
523   {"[_3 0 1]", /* _8 0 1 */},
524   {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
525   {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
526
527   {"[4]", /* 4 */ "4 cascade"},
528   {"[+4 3]", /* +5 3 */ "4 ball half shower"},
529   {"[4 4 2]", /* 5 5 2 */},
530   {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
531   {"[+4 3 +4]", /* +5 3 +4 */},
532   {"[4 3 4 4]", /* 5 3 4 4 */},
533   {"[4 3 3 4]", /* 6 3 3 4 */},
534   {"[4 3 2 4", /* 6 4 2 4 */},
535   {"[+4 1]", /* +7 1 */ "4 shower"},
536   {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
537   {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
538   {"[+4 2 1 3]", /* +9 3 1 3 */},
539   {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
540   {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
541   {"[_4 3 3]", /* _6 3 3 */},
542   {"[_4 3 1]", /* _7 4 1 */},
543   {"[_4 2 1]", /* _8 3 1 */},
544   {"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
545   {"[_4 1 3 1]", /* _9 1 5 1 */},
546   {"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
547
548   {"[5]", /* 5 */ "5 cascade"},
549   {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
550   {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
551   {"[5 4 4]", /* 7 4 4 */},
552   {"[_5 4 4]", /* _7 4 4 */},
553   {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
554   {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
555   {"[_5 4 1 +4]", /* _9 5 1 5 */},
556   {"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
557   {"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
558   {"[_5 4 4 5 1]",},
559   {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
560
561   {"[6]", /* 6 */ "6 cascade"},
562   {"[+6 5]", /* +7 5 */},
563   {"[6 4]", /* 8 4 */},
564   {"[+6 3]", /* +9 3 */},
565   {"[6 5 4 4]", /* 9 7 4 4 */},
566   {"[+6 5 5 5]", /* +9 5 5 5 */},
567   {"[6 0 6]", /* 9 0 9 */},
568   {"[_6 0 _6]", /* _9 0 _9 */},
569
570   {"[_7]", /* _7 */ "bouncing 7 cascade"},
571   {"[7]", /* 7 */ "7 cascade"},
572   {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
573
574 };
575
576
577
578 typedef struct { int start; int number; } PatternIndex;
579
580 struct patternindex {
581   int minballs;
582   int maxballs;
583   PatternIndex index[XtNumber(portfolio)];
584 };
585
586
587 /* Jugglestruct: per-screen global data.  The master Wander, Object
588  * and Trajectory lists are anchored here. */
589 typedef struct {
590   double        scale;
591   Wander       *wander;
592   double        cx;
593   double        Gr;
594   Trajectory   *head;
595   Arm           arm[2][2];
596   char         *pattern;
597   int           count;
598   int           num_balls;
599   time_t        begintime; /* should make 'time' usable for at least 48 days
600                                                         on a 32-bit machine */
601   unsigned long time; /* millisecond timer*/
602   ObjType       objtypes;
603   Object       *objects;
604   struct patternindex patternindex;
605   XFontStruct *mode_font;
606 } jugglestruct;
607
608 static jugglestruct *juggles = (jugglestruct *) NULL;
609
610 /*******************
611  * list management *
612  *******************/
613
614 #define DUP_OBJECT(n, t) { \
615   (n)->object = (t)->object; \
616   if((n)->object != NULL) (n)->object->count++; \
617 }
618
619 /* t must point to an existing element.  t must not be an
620    expression ending ->next or ->prev */
621 #define REMOVE(t) { \
622   (t)->next->prev = (t)->prev; \
623   (t)->prev->next = (t)->next; \
624   free(t); \
625 }
626
627 /* t receives element to be created and added to the list.  ot must
628    point to an existing element or be identical to t to start a new
629    list. Applicable to Trajectories, Objects and Traces. */
630 #define ADD_ELEMENT(type, t, ot) \
631   if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
632     (t)->next = (ot)->next; \
633     (t)->prev = (ot); \
634     (ot)->next = (t); \
635     (t)->next->prev = (t); \
636   }
637
638 static void
639 object_destroy(Object* o)
640 {
641   if(o->trace != NULL) {
642         while(o->trace->next != o->trace) {
643           Trace *s = o->trace->next;
644           REMOVE(s); /* Don't eliminate 's' */
645         }
646         free(o->trace);
647   }
648   REMOVE(o);
649 }
650
651 static void
652 trajectory_destroy(Trajectory *t) {
653   if(t->name != NULL) free(t->name);
654   if(t->pattern != NULL) free(t->pattern);
655   /* Reduce object link count and call destructor if necessary */
656   if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
657         object_destroy(t->object);
658   }
659   REMOVE(t); /* Unlink and free */
660 }
661
662 static void
663 free_juggle(jugglestruct *sp) {
664   if (sp->head != NULL) {
665         while (sp->head->next != sp->head) {
666           trajectory_destroy(sp->head->next);
667         }
668         free(sp->head);
669         sp->head = (Trajectory *) NULL;
670   }
671   if(sp->objects != NULL) {
672         while (sp->objects->next != sp->objects) {
673           object_destroy(sp->objects->next);
674         }
675         free(sp->objects);
676         sp->objects = (Object*)NULL;
677   }
678   if(sp->wander != NULL) {
679         while (sp->wander->next != sp->wander) {
680           Wander *w = sp->wander->next;
681           REMOVE(w);
682         }
683         free(sp->wander);
684         sp->wander = (Wander*)NULL;
685   }
686   if(sp->pattern != NULL) {
687         free(sp->pattern);
688         sp->pattern = NULL;
689   }
690   if (sp->mode_font!=None) {
691         XFreeFontInfo(NULL,sp->mode_font,1);
692         sp->mode_font = None;
693   }
694 }
695
696 static Bool
697 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
698 {
699   Trajectory *t;
700
701   ADD_ELEMENT(Trajectory, t, sp->head->prev);
702   if(t == NULL){ /* Out of Memory */
703         free_juggle(sp);
704         return False;
705   }
706   t->object = NULL;
707   if(name != NULL)
708         t->name = strdup(name);
709   t->posn = type;
710   if (n == ADAM) {
711         t->adam = h;
712         t->height = 0;
713         t->status = ATCH;
714   } else {
715         t->height = h;
716         t->status = THRATCH;
717   }
718   return True;
719 }
720
721 /* add a Thratch to the performance */
722 static Bool
723 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
724 {
725   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
726   const char *p;
727   int w, h, i, seen;
728   Notation notation;
729   char type;
730
731   if (MI_IS_VERBOSE(mi)) {
732         (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
733                                    MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
734   }
735
736   for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
737                                                                                 have been programmed */
738         /* title is the pattern name to be supplied to the first throw of
739            a sequence.  If no name if given, use an empty title so that
740            the sequences are still delimited. */
741         const char *title = (name != NULL)? name : "";
742         type=' ';
743         h = 0;
744         seen = 0;
745         notation = HEIGHT;
746         for(p=patn; *p; p++) {
747           if (*p >= '0' && *p <='9') {
748                 seen = 1;
749                 h = 10*h + (*p - '0');
750           } else {
751                 Notation nn = notation;
752                 switch (*p) {
753                 case '[':            /* begin Adam notation */
754                   notation = ADAM;
755                   break;
756                 case '-':            /* Inside throw */
757                   type = ' ';
758                   break;
759                 case '+':            /* Outside throw */
760                 case '=':            /* Cross throw */
761                 case '&':            /* Cross catch */
762                 case 'x':            /* Cross throw and catch */
763                 case '_':            /* Bounce */
764                 case 'k':            /* Kickup */
765                   type = *p;
766                   break;
767                 case '*':            /* Lose ball */
768                   seen = 1;
769                   h = -1;
770                   /* fall through */
771                 case ']':             /* end Adam notation */
772                   nn = HEIGHT;
773                   /* fall through */
774                 case ' ':
775                   if (seen) {
776                         i++;
777                         if (!add_throw(sp, type, h, notation, title))
778                                 return False;
779                         title = NULL;
780                         type=' ';
781                         h = 0;
782                         seen = 0;
783                   }
784                   notation = nn;
785                   break;
786                 default:
787                   if(w == 0) { /* Only warn on first pass */
788                         (void) fprintf(stderr,
789                                                    "juggle[%d]: Unexpected pattern instruction: '%c'\n",
790                                                    MI_SCREEN(mi), *p);
791                   }
792                   break;
793                 }
794           }
795         }
796         if (seen) { /* end of sequence */
797           if (!add_throw(sp, type, h, notation, title))
798                 return False;
799           title = NULL;
800         }
801   }
802   return True;
803 }
804
805 /*
806  ~~~~\~~~~~\~~~
807  \\~\\~\~\\\~~~
808  \\~\\\\~\\\~\~
809  \\\\\\\\\\\~\\
810
811 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
812
813 4 4 1 3 12 2 4 1 4 4 13 0 3 1
814
815 */
816 #define BOUNCEOVER 10
817 #define KICKMIN 7
818 #define THROWMAX 20
819
820 /* Convert Adam notation into heights */
821 static void
822 adam(jugglestruct *sp)
823 {
824   Trajectory *t, *p;
825   for(t = sp->head->next; t != sp->head; t = t->next) {
826         if (t->status == ATCH) {
827           int a = t->adam;
828           t->height = 0;
829           for(p = t->next; a > 0; p = p->next) {
830                 if(p == sp->head) {
831                   t->height = -9; /* Indicate end of processing for name() */
832                   return;
833                 }
834                 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
835                   a--;
836                 }
837                 t->height++;
838           }
839           if(t->height > BOUNCEOVER && t->posn == ' '){
840                 t->posn = '_'; /* high defaults can be bounced */
841           } else if(t->height < 3 && t->posn == '_') {
842                 t->posn = ' '; /* Can't bounce short throws. */
843           }
844           if(t->height < KICKMIN && t->posn == 'k'){
845                 t->posn = ' '; /* Can't kick short throws */
846           }
847           if(t->height > THROWMAX){
848                 t->posn = 'k'; /* Use kicks for ridiculously high throws */
849           }
850           t->status = THRATCH;
851         }
852   }
853 }
854
855 /* Discover converted heights and update the sequence title */
856 static void
857 name(jugglestruct *sp)
858 {
859   Trajectory *t, *p;
860   char buffer[BUFSIZ];
861   char *b;
862   for(t = sp->head->next; t != sp->head; t = t->next) {
863         if (t->status == THRATCH && t->name != NULL) {
864           b = buffer;
865           for(p = t; p == t || p->name == NULL; p = p->next) {
866                 if(p == sp->head || p->height < 0) { /* end of reliable data */
867                   return;
868                 }
869                 if(p->posn == ' ') {
870                   b += sprintf(b, " %d", p->height);
871                 } else {
872                   b += sprintf(b, " %c%d", p->posn, p->height);
873                 }
874                 if(b - buffer > 500) break; /* otherwise this could eventually
875                                                                            overflow.  It'll be too big to
876                                                                            display anyway. */
877           }
878           if(*t->name != 0) {
879                 (void) sprintf(b, ", %s", t->name);
880           }
881           free(t->name); /* Don't need name any more, it's been converted
882                                                 to pattern */
883           t->name = NULL;
884           if(t->pattern != NULL) free(t->pattern);
885           t->pattern = strdup(buffer);
886         }
887   }
888 }
889
890 /* Split Thratch notation into explicit throws and catches.
891    Usually Catch follows Throw in same hand, but take care of special
892    cases. */
893
894 /* ..n1.. -> .. LTn RT1 LC RC .. */
895 /* ..nm.. -> .. LTn LC RTm RC .. */
896
897 static Bool
898 part(jugglestruct *sp)
899 {
900   Trajectory *t, *nt, *p;
901   Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
902
903   for (t = sp->head->next; t != sp->head; t = t->next) {
904         if (t->status > THRATCH) {
905           hand = t->hand;
906         } else if (t->status == THRATCH) {
907           char posn = '=';
908
909           /* plausibility check */
910           if (t->height <= 2 && t->posn == '_') {
911                 t->posn = ' '; /* no short bounces */
912           }
913           if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
914                 t->posn = ' '; /* 1's need close catches */
915           }
916
917           switch (t->posn) {
918                   /*         throw          catch    */
919           case ' ': posn = '-'; t->posn = '+'; break;
920           case '+': posn = '+'; t->posn = '-'; break;
921           case '=': posn = '='; t->posn = '+'; break;
922           case '&': posn = '+'; t->posn = '='; break;
923           case 'x': posn = '='; t->posn = '='; break;
924           case '_': posn = '_'; t->posn = '-'; break;
925           case 'k': posn = 'k'; t->posn = 'k'; break;
926           default:
927                 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
928                 break;
929           }
930           hand = (Hand) ((hand + 1) % 2);
931           t->status = ACTION;
932           t->hand = hand;
933           p = t->prev;
934
935           if (t->height == 1 && p != sp->head) {
936                 p = p->prev; /* '1's are thrown earlier than usual */
937           }
938
939
940
941           t->action = CATCH;
942           ADD_ELEMENT(Trajectory, nt, p);
943           if(nt == NULL){
944                 free_juggle(sp);
945                 return False;
946           }
947           nt->object = NULL;
948           nt->status = ACTION;
949           nt->action = THROW;
950           nt->height = t->height;
951           nt->hand = hand;
952           nt->posn = posn;
953
954         }
955   }
956   return True;
957 }
958
959 static ObjType
960 choose_object(void) {
961   ObjType o;
962   for (;;) {
963         o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
964         if(balls && o == BALL) break;
965         if(clubs && o == CLUB) break;
966         if(torches && o == TORCH) break;
967         if(knives && o == KNIFE) break;
968         if(rings && o == RING) break;
969         if(bballs && o == BBALLS) break;
970   }
971   return o;
972 }
973
974 /* Connnect up throws and catches to figure out which ball goes where.
975    Do the same with the juggler's hands. */
976
977 static void
978 lob(ModeInfo *mi)
979 {
980   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
981   Trajectory *t, *p;
982   int h;
983   for (t = sp->head->next; t != sp->head; t = t->next) {
984         if (t->status == ACTION) {
985           if (t->action == THROW) {
986                 if (t->type == Empty) {
987                   /* Create new Object */
988                   ADD_ELEMENT(Object, t->object, sp->objects);
989                   t->object->count = 1;
990                   t->object->tracelen = 0;
991                   t->object->active = False;
992                   /* Initialise object's circular trace list */
993                   ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
994
995                   if (MI_NPIXELS(mi) > 2) {
996                         t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
997                   } else {
998 #ifdef STANDALONE
999                         t->object->color = 1;
1000 #else
1001                         t->object->color = 0;
1002 #endif
1003                   }
1004
1005                   /* Small chance of picking a random object instead of the
1006                          current theme. */
1007                   if(NRAND(OBJMIXPROB) == 0) {
1008                         t->object->type = choose_object();
1009                   } else {
1010                         t->object->type = sp->objtypes;
1011                   }
1012
1013                   /* Check to see if we need trails for this object */
1014                   if(tail < ObjectDefs[t->object->type].mintrail) {
1015                         t->object->tail = ObjectDefs[t->object->type].mintrail;
1016                   } else {
1017                         t->object->tail = tail;
1018                   }
1019                 }
1020
1021                 /* Balls can change divisions at each throw */
1022                 t->divisions = 2 * (NRAND(2) + 1);
1023
1024                 /* search forward for next catch in this hand */
1025                 for (p = t->next; t->handlink == NULL; p = p->next) {
1026                   if(p->status < ACTION || p == sp->head) return;
1027                   if (p->action == CATCH) {
1028                         if (t->handlink == NULL && p->hand == t->hand) {
1029                           t->handlink = p;
1030                         }
1031                   }
1032                 }
1033
1034                 if (t->height > 0) {
1035                   h = t->height - 1;
1036
1037                   /* search forward for next ball catch */
1038                   for (p = t->next; t->balllink == NULL; p = p->next) {
1039                         if(p->status < ACTION || p == sp->head) {
1040                           t->handlink = NULL;
1041                           return;
1042                         }
1043                         if (p->action == CATCH) {
1044                           if (t->balllink == NULL && --h < 1) { /* caught */
1045                                 t->balllink = p; /* complete trajectory */
1046 # if 0
1047                                 if (p->type == Full) {
1048                                   (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1049                                                   MI_SCREEN(mi), t->object->color);
1050                                 }
1051 #endif
1052                                 p->type = Full;
1053                                 DUP_OBJECT(p, t); /* accept catch */
1054                                 p->angle = t->angle;
1055                                 p->divisions = t->divisions;
1056                           }
1057                         }
1058                   }
1059                 }
1060                 t->type = Empty; /* thrown */
1061           } else if (t->action == CATCH) {
1062                 /* search forward for next throw from this hand */
1063                 for (p = t->next; t->handlink == NULL; p = p->next) {
1064                   if(p->status < ACTION || p == sp->head) return;
1065                   if (p->action == THROW && p->hand == t->hand) {
1066                         p->type = t->type; /* pass ball */
1067                         DUP_OBJECT(p, t); /* pass object */
1068                         p->divisions = t->divisions;
1069                         t->handlink = p;
1070                   }
1071                 }
1072           }
1073           t->status = LINKEDACTION;
1074         }
1075   }
1076 }
1077
1078 /* Clap when both hands are empty */
1079 static void
1080 clap(jugglestruct *sp)
1081 {
1082   Trajectory *t, *p;
1083   for (t = sp->head->next; t != sp->head; t = t->next) {
1084         if (t->status == LINKEDACTION &&
1085                 t->action == CATCH &&
1086                 t->type == Empty &&
1087                 t->handlink != NULL &&
1088                 t->handlink->height == 0) { /* Completely idle hand */
1089
1090           for (p = t->next; p != sp->head; p = p->next) {
1091                 if (p->status == LINKEDACTION &&
1092                         p->action == CATCH &&
1093                         p->hand != t->hand) { /* Next catch other hand */
1094                   if(p->type == Empty &&
1095                          p->handlink != NULL &&
1096                          p->handlink->height == 0) { /* Also completely idle */
1097
1098                         t->handlink->posn = '^'; /* Move first hand's empty throw */
1099                         p->posn = '^';           /* to meet second hand's empty
1100                                                                                 catch */
1101
1102                   }
1103                   break; /* Only need first catch */
1104                 }
1105           }
1106         }
1107   }
1108 }
1109
1110 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1111
1112 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1113    with velocity dx1 at time t1 */
1114 static Spline
1115 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1116 {
1117   Spline s;
1118   double a, b, c, d;
1119   double x10;
1120   double t10;
1121
1122   x10 = x1 - x0;
1123   t10 = t1 - t0;
1124   a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1125   b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1126   c = dx0;
1127   d = x0;
1128   s.a = a;
1129   s.b = -3*a*t0 + b;
1130   s.c = (3*a*t0 - 2*b)*t0 + c;
1131   s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1132   return s;
1133 }
1134
1135 /* Compute a pair of splines.  s1 goes from x0 vith velocity dx0 at
1136    time t0 to x1 at time t1.  s2 goes from x1 at time t1 to x2 with
1137    velocity dx2 at time t2.  The arrival and departure velocities at
1138    x1, t1 must be the same. */
1139 static double
1140 makeSplinePair(Spline *s1, Spline *s2,
1141                            double x0, double dx0, int t0,
1142                            double x1,             int t1,
1143                            double x2, double dx2, int t2)
1144 {
1145   double x10, x21, t21, t10, t20, dx1;
1146   x10 = x1 - x0;
1147   x21 = x2 - x1;
1148   t21 = t2 - t1;
1149   t10 = t1 - t0;
1150   t20 = t2 - t0;
1151   dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1152                  - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1153         (2*t10*t21*t20);
1154   *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1155   *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1156   return dx1;
1157 }
1158
1159 /* Compute a Ballistic path in a pair of degenerate splines.  sx goes
1160    from x at time t at constant velocity dx.  sy goes from y at time t
1161    with velocity dy and constant acceleration g. */
1162 static void
1163 makeParabola(Trajectory *n,
1164                          double x, double dx, double y, double dy, double g)
1165 {
1166   double t = (double)n->start;
1167   n->xp.a = 0;
1168   n->xp.b = 0;
1169   n->xp.c = dx;
1170   n->xp.d = -dx*t + x;
1171   n->yp.a = 0;
1172   n->yp.b = g/2;
1173   n->yp.c = -g*t + dy;
1174   n->yp.d = g/2*t*t - dy*t + y;
1175 }
1176
1177
1178
1179 /* Make juggler wander around the screen */
1180 static double wander(jugglestruct *sp, unsigned long time)
1181 {
1182   Wander *w = NULL;
1183   for (w = sp->wander->next; w != sp->wander; w = w->next) {
1184         if (w->finish < sp->time) { /* expired */
1185           Wander *ww = w;
1186           w = w->prev;
1187           REMOVE(ww);
1188         } else if(w->finish > time) {
1189           break;
1190         }
1191   }
1192   if(w == sp->wander) { /* Need a new one */
1193         ADD_ELEMENT(Wander, w, sp->wander->prev);
1194         if(w == NULL) { /* Memory problem */
1195           return 0.0;
1196         }
1197         w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1198         if(time == 0) {
1199           w->x = 0;
1200         } else {
1201           w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1202         }
1203         w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1204   }
1205   return CUBIC(w->s, time);
1206 }
1207
1208 #define SX 25 /* Shoulder Width */
1209
1210 /* Convert hand position symbols into actual time/space coordinates */
1211 static void
1212 positions(jugglestruct *sp)
1213 {
1214   Trajectory *t;
1215   unsigned long now = sp->time; /* Make sure we're not lost in the past */
1216   for (t = sp->head->next; t != sp->head; t = t->next) {
1217         if (t->status >= PTHRATCH) {
1218           now = t->start;
1219         } else if (t->status == ACTION || t->status == LINKEDACTION) {
1220           /* Allow ACTIONs to be annotated, but we won't mark them ready
1221                  for the next stage */
1222
1223           double xo = 0, yo;
1224           double sx = SX;
1225           double pose = SX/2;
1226
1227           /* time */
1228           if (t->action == CATCH) { /* Throw-to-catch */
1229                 if (t->type == Empty) {
1230                   now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1231                 } else {     /* successful catch */
1232                   now += (int)(THROW_CATCH_INTERVAL);
1233                 }
1234           } else { /* Catch-to-throw */
1235                 if(t->object != NULL) {
1236                   now += (int) (CATCH_THROW_INTERVAL *
1237                                                 ObjectDefs[t->object->type].weight);
1238                 } else {
1239                   now += (int) (CATCH_THROW_INTERVAL);
1240                 }
1241           }
1242
1243           if(t->start == 0)
1244                 t->start = now;
1245           else /* Concatenated performances may need clock resync */
1246                 now = t->start;
1247
1248           t->cx = wander(sp, t->start);
1249
1250           /* space */
1251           yo = 90;
1252
1253           /* Add room for the handle */
1254           if(t->action == CATCH && t->object != NULL)
1255                 yo -= ObjectDefs[t->object->type].handle;
1256
1257           switch (t->posn) {
1258           case '-': xo = sx - pose; break;
1259           case '_':
1260           case 'k':
1261           case '+': xo = sx + pose; break;
1262           case '~':
1263           case '=': xo = - sx - pose; yo += pose; break;
1264           case '^': xo = 0; yo += pose*2; break; /* clap */
1265           default:
1266                 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1267                 break;
1268           }
1269
1270           t->angle = (((t->hand == LEFT) ^
1271                                    (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1272                                         -1 : 1) * M_PI/2;
1273
1274           t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1275           t->y = yo;
1276
1277           /* Only mark complete if it was already linked */
1278           if(t->status == LINKEDACTION) {
1279                 t->status = PTHRATCH;
1280           }
1281         }
1282   }
1283 }
1284
1285
1286 /* Private physics functions */
1287
1288 /* Compute the spin-rate for a trajectory.  Different types of throw
1289    (eg, regular thows, bounces, kicks, etc) have different spin
1290    requirements.
1291
1292    type = type of object
1293    h = trajectory of throwing hand (throws), or next throwing hand (catches)
1294    old = earlier spin to consider
1295    dt = time span of this trajectory
1296    height = height of ball throw or 0 if based on old spin
1297    turns = full club turns required during this operation
1298    togo = partial club turns required to match hands
1299 */
1300 static double
1301 spinrate(ObjType type, Trajectory *h, double old, double dt,
1302                  int height, int turns, double togo)
1303 {
1304   const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1305
1306   if(ObjectDefs[type].handle != 0) { /* Clubs */
1307         return (dir * turns * 2 * M_PI + togo) / dt;
1308   } else if(height == 0) { /* Balls already spinning */
1309         return old/2;
1310   } else { /* Balls */
1311         return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1312   }
1313 }
1314
1315
1316 /* compute the angle at the end of a spinning trajectory */
1317 static double
1318 end_spin(Trajectory *t)
1319 {
1320   return t->angle + t->spin * (t->finish - t->start);
1321 }
1322
1323 /* Sets the initial angle of the catch following hand movement t to
1324    the final angle of the throw n.  Also sets the angle of the
1325    subsequent throw to the same angle plus half a turn. */
1326 static void
1327 match_spins_on_catch(Trajectory *t, Trajectory *n)
1328 {
1329   if(ObjectDefs[t->balllink->object->type].handle == 0) {
1330         t->balllink->angle = end_spin(n);
1331         if(t->balllink->handlink != NULL) {
1332           t->balllink->handlink->angle = t->balllink->angle + M_PI;
1333         }
1334   }
1335 }
1336
1337 static double
1338 find_bounce(jugglestruct *sp,
1339                         double yo, double yf, double yc, double tc, double cor)
1340 {
1341   double tb, i, dy = 0;
1342   const double e = 1; /* permissible error in yc */
1343
1344   /*
1345         tb = time to bounce
1346         yt = height at catch time after one bounce
1347         one or three roots according to timing
1348         find one by interval bisection
1349   */
1350   tb = tc;
1351   for(i = tc / 2; i > 0.0001; i/=2){
1352         double dt, yt;
1353         if(tb == 0){
1354           (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1355           break;
1356         }
1357         dy = (yf - yo)/tb + sp->Gr/2*tb;
1358         dt = tc - tb;
1359         yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1360         if(yt < yc + e){
1361           tb-=i;
1362         }else if(yt > yc - e){
1363           tb+=i;
1364         }else{
1365           break;
1366         }
1367   }
1368   if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1369         tb = -1;
1370   }
1371   return tb;
1372 }
1373
1374 static Trajectory*
1375 new_predictor(const Trajectory *t, int start, int finish, double angle)
1376 {
1377   Trajectory *n;
1378   ADD_ELEMENT(Trajectory, n, t->prev);
1379   if(n == NULL){
1380         return NULL;
1381   }
1382   DUP_OBJECT(n, t);
1383   n->divisions = t->divisions;
1384   n->type = Ball;
1385   n->status = PREDICTOR;
1386
1387   n->start = start;
1388   n->finish = finish;
1389   n->angle = angle;
1390   return n;
1391 }
1392
1393 /* Turn abstract timings into physically appropriate object trajectories. */
1394 static Bool
1395 projectile(jugglestruct *sp)
1396 {
1397   Trajectory *t;
1398   const int yf = 0; /* Floor height */
1399
1400   for (t = sp->head->next; t != sp->head; t = t->next) {
1401         if (t->status != PTHRATCH || t->action != THROW) {
1402           continue;
1403         } else if (t->balllink == NULL) { /* Zero Throw */
1404           t->status = BPREDICTOR;
1405         } else if (t->balllink->handlink == NULL) { /* Incomplete */
1406           return True;
1407         } else if(t->balllink == t->handlink) {
1408           /* '2' height - hold on to ball.  Don't need to consider
1409                  flourishes, 'hands' will do that automatically anyway */
1410
1411           t->type = Full;
1412           /* Zero spin to avoid wrist injuries */
1413           t->spin = 0;
1414           match_spins_on_catch(t, t);
1415           t->dx = t->dy = 0;
1416           t->status = BPREDICTOR;
1417           continue;
1418         } else {
1419           if (t->posn == '_') { /* Bounce once */
1420
1421                 const int tb = t->start +
1422                   find_bounce(sp, t->y, (double) yf, t->balllink->y,
1423                                           (double) (t->balllink->start - t->start),
1424                                           ObjectDefs[t->object->type].cor);
1425
1426                 if(tb < t->start) { /* bounce too hard */
1427                   t->posn = '+'; /* Use regular throw */
1428                 } else {
1429                   Trajectory *n; /* First (throw) trajectory. */
1430                   double dt; /* Time span of a trajectory */
1431                   double dy; /* Distance span of a follow-on trajectory.
1432                                                 First trajectory uses t->dy */
1433                   /* dx is constant across both trajectories */
1434                   t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1435
1436                   { /* ball follows parabola down */
1437                         n = new_predictor(t, t->start, tb, t->angle);
1438                         if(n == NULL) return False;
1439                         dt = n->finish - n->start;
1440                         /* Ball rate 4, no flight or matching club turns */
1441                         n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1442                         t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1443                         makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1444                   }
1445
1446                   { /* ball follows parabola up */
1447                         Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1448                                                                                   end_spin(n));
1449                         if(m == NULL) return False;
1450                         dt = m->finish - m->start;
1451                         /* Use previous ball rate, no flight club turns */
1452                         m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1453                                                            t->balllink->angle - m->angle);
1454                         match_spins_on_catch(t, m);
1455                         dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1456                         makeParabola(m, t->balllink->x - t->dx * dt,
1457                                                  t->dx, (double) yf, dy, sp->Gr);
1458                   }
1459
1460                   t->status = BPREDICTOR;
1461                   continue;
1462                 }
1463           } else if (t->posn == 'k') { /* Drop & Kick */
1464                 Trajectory *n; /* First (drop) trajectory. */
1465                 Trajectory *o; /* Second (rest) trajectory */
1466                 Trajectory *m; /* Third (kick) trajectory */
1467                 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1468                 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1469                 double dt, dy;
1470
1471                 { /* Fall to ground */
1472                   n = new_predictor(t, t->start, td, t->angle);
1473                   if(n == NULL) return False;
1474                   dt = n->finish - n->start;
1475                   /* Ball spin rate 4, no flight club turns */
1476                   n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1477                                                          t->balllink->angle - n->angle);
1478                   t->dx = (t->balllink->x - t->x) / dt;
1479                   t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1480                   makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1481                 }
1482
1483                 { /* Rest on ground */
1484                   o = new_predictor(t, n->finish, tk, end_spin(n));
1485                   if(o == NULL) return False;
1486                   o->spin = 0;
1487                   makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1488                 }
1489
1490                 /* Kick up */
1491                 {
1492                   m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1493                   if(m == NULL) return False;
1494                   dt = m->finish - m->start;
1495                   /* Match receiving hand, ball rate 4, one flight club turn */
1496                   m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1497                                                          4, 1, t->balllink->angle - m->angle);
1498                   match_spins_on_catch(t, m);
1499                   dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1500                   makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1501                 }
1502
1503                 t->status = BPREDICTOR;
1504                 continue;
1505           }
1506
1507           /* Regular flight, no bounce */
1508           { /* ball follows parabola */
1509                 double dt;
1510                 Trajectory *n = new_predictor(t, t->start,
1511                                                                           t->balllink->start, t->angle);
1512                 if(n == NULL) return False;
1513                 dt = t->balllink->start - t->start;
1514                 /* Regular spin */
1515                 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1516                                                    t->balllink->angle - n->angle);
1517                 match_spins_on_catch(t, n);
1518                 t->dx = (t->balllink->x - t->x) / dt;
1519                 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1520                 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1521           }
1522
1523           t->status = BPREDICTOR;
1524         }
1525   }
1526   return True;
1527 }
1528
1529 /* Turn abstract hand motions into cubic splines. */
1530 static void
1531 hands(jugglestruct *sp)
1532 {
1533   Trajectory *t, *u, *v;
1534
1535   for (t = sp->head->next; t != sp->head; t = t->next) {
1536         /* no throw => no velocity */
1537         if (t->status != BPREDICTOR) {
1538           continue;
1539         }
1540
1541         u = t->handlink;
1542         if (u == NULL) { /* no next catch */
1543           continue;
1544         }
1545         v = u->handlink;
1546         if (v == NULL) { /* no next throw */
1547           continue;
1548         }
1549
1550         /* double spline takes hand from throw, thru catch, to
1551            next throw */
1552
1553         t->finish = u->start;
1554         t->status = PREDICTOR;
1555
1556         u->finish = v->start;
1557         u->status = PREDICTOR;
1558
1559
1560         /* FIXME: These adjustments leave a small glitch when alternating
1561            balls and clubs.  Just hope no-one notices.  :-) */
1562
1563         /* make sure empty hand spin matches the thrown object in case it
1564            had a handle */
1565
1566         t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1567           fabs((u->angle - t->angle)/(u->start - t->start));
1568
1569         u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1570           fabs((v->angle - u->angle)/(v->start - u->start));
1571
1572         (void) makeSplinePair(&t->xp, &u->xp,
1573                                                   t->x, t->dx, t->start,
1574                                                   u->x, u->start,
1575                                                   v->x, v->dx, v->start);
1576         (void) makeSplinePair(&t->yp, &u->yp,
1577                                                   t->y, t->dy, t->start,
1578                                                   u->y, u->start,
1579                                                   v->y, v->dy, v->start);
1580
1581         t->status = PREDICTOR;
1582   }
1583 }
1584
1585 /* Given target x, y find_elbow puts hand at target if possible,
1586  * otherwise makes hand point to the target */
1587 static void
1588 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1589                    int z)
1590 {
1591   double r, h2, t;
1592   double x = p->x - s->x;
1593   double y = p->y - s->y;
1594   h2 = x*x + y*y + z*z;
1595   if (h2 > 4 * armlength * armlength) {
1596         t = armlength/sqrt(h2);
1597         e->x = t*x + s->x;
1598         e->y = t*y + s->y;
1599         h->x = 2 * t * x + s->x;
1600         h->y = 2 * t * y + s->y;
1601   } else {
1602         r = sqrt((double)(x*x + z*z));
1603         t = sqrt(4 * armlength * armlength / h2 - 1);
1604         e->x = x*(1 + y*t/r)/2 + s->x;
1605         e->y = (y - r*t)/2 + s->y;
1606         h->x = x + s->x;
1607         h->y = y + s->y;
1608   }
1609 }
1610
1611
1612 /* NOTE: returned x, y adjusted for arm reach */
1613 static void
1614 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1615 {
1616   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1617   DXPoint h, e;
1618   find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1619   *p = sp->arm[1][side][HAND] = h;
1620   sp->arm[1][side][ELBOW] = e;
1621 }
1622
1623 #if DEBUG
1624 /* dumps a human-readable rendition of the current state of the juggle
1625    pipeline to stderr for debugging */
1626 static void
1627 dump(jugglestruct *sp)
1628 {
1629   Trajectory *t;
1630   for (t = sp->head->next; t != sp->head; t = t->next) {
1631         switch (t->status) {
1632         case ATCH:
1633           (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1634           break;
1635         case THRATCH:
1636           (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1637                                          t->pattern == NULL?"":t->pattern);
1638           break;
1639         case ACTION:
1640           if (t->action == CATCH)
1641             (void) fprintf(stderr, "%p A %c%cC\n",
1642                                          (void*)t, t->posn,
1643                                          t->hand ? 'R' : 'L');
1644           else
1645             (void) fprintf(stderr, "%p A %c%c%c%d\n",
1646                                          (void*)t, t->posn,
1647                                          t->hand ? 'R' : 'L',
1648                                          (t->action == THROW)?'T':'N',
1649                                          t->height);
1650           break;
1651         case LINKEDACTION:
1652           (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1653                                          (void*)t, t->posn,
1654                                          t->hand?'R':'L',
1655                                          (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1656                                          t->height, t->object == NULL?0:t->object->color,
1657                                          (void*)t->handlink, (void*)t->balllink);
1658           break;
1659         case PTHRATCH:
1660           (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1661                                          (void*)t, t->posn,
1662                                          t->hand?'R':'L',
1663                                          (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1664                                          t->height, t->type, t->object == NULL?0:t->object->color,
1665                                          t->start, t->finish);
1666           break;
1667         case BPREDICTOR:
1668           (void) fprintf(stderr, "%p B %c      %2d %6lu %6lu %g\n",
1669                                          (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1670                                          t->object == NULL?0:t->object->color,
1671                                          t->start, t->finish, t->yp.c);
1672           break;
1673         case PREDICTOR:
1674           (void) fprintf(stderr, "%p P %c      %2d %6lu %6lu %g\n",
1675                                          (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1676                                          t->object == NULL?0:t->object->color,
1677                                          t->start, t->finish, t->yp.c);
1678           break;
1679         default:
1680           (void) fprintf(stderr, "%p: status %d not implemented\n",
1681                                          (void*)t, t->status);
1682           break;
1683         }
1684   }
1685   (void) fprintf(stderr, "---\n");
1686 }
1687 #endif
1688
1689 static int get_num_balls(const char *j)
1690 {
1691   int balls = 0;
1692   const char *p;
1693   int h = 0;
1694   if (!j) abort();
1695   for (p = j; *p; p++) {
1696         if (*p >= '0' && *p <='9') { /* digit */
1697           h = 10*h + (*p - '0');
1698         } else {
1699           if (h > balls) {
1700                 balls = h;
1701           }
1702           h = 0;
1703         }
1704   }
1705   return balls;
1706 }
1707
1708 #ifdef __cplusplus
1709 extern "C" {
1710 #endif
1711
1712 static int
1713 compare_num_balls(const void *p1, const void *p2)
1714 {
1715   int i, j;
1716   i = get_num_balls(((patternstruct*)p1)->pattern);
1717   j = get_num_balls(((patternstruct*)p2)->pattern);
1718   if (i > j) {
1719         return (1);
1720   } else if (i < j) {
1721         return (-1);
1722   } else {
1723         return (0);
1724   }
1725 }
1726
1727 #ifdef __cplusplus
1728 }
1729 #endif
1730
1731
1732 /**************************************************************************
1733  *                        Rendering Functions                             *
1734  *                                                                        *
1735  **************************************************************************/
1736
1737 static void
1738 show_arms(ModeInfo * mi, unsigned long color)
1739 {
1740   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1741   unsigned int i, j;
1742   Hand side;
1743   XPoint a[XtNumber(sp->arm[0][0])];
1744   if(color == MI_BLACK_PIXEL(mi)) {
1745         j = 0;
1746   } else {
1747         j = 1;
1748   }
1749   XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1750                                          ARMWIDTH, LineSolid, CapRound, JoinRound);
1751   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1752   for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1753         /* Translate into device coords */
1754         for(i = 0; i < XtNumber(a); i++) {
1755           a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1756           a[i].y = (short)(MI_HEIGHT(mi)  - sp->arm[j][side][i].y*sp->scale);
1757           if(j == 1)
1758                 sp->arm[0][side][i] = sp->arm[1][side][i];
1759         }
1760         XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1761                            a, XtNumber(a), CoordModeOrigin);
1762   }
1763 }
1764
1765 static void
1766 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1767 {
1768   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1769   XPoint p[7];
1770   unsigned int i;
1771
1772   /*      +-----+ 9
1773           |  6  |
1774        10 +--+--+
1775        2 +---+---+ 3
1776           \  5  /
1777            \   /
1778             \ /
1779            1 +
1780             / \
1781            /   \
1782         0 +-----+ 4
1783           |     |
1784           |     |
1785           |     |
1786         7 +     + 8
1787   */
1788
1789   static const XPoint figure[] = {
1790         { 15,  70}, /* 0  Left Hip */
1791         {  0,  90}, /* 1  Waist */
1792         { SX, 130}, /* 2  Left Shoulder */
1793         {-SX, 130}, /* 3  Right Shoulder */
1794         {-15,  70}, /* 4  Right Hip */
1795         {  0, 130}, /* 5  Neck */
1796         {  0, 140}, /* 6  Chin */
1797         { SX,   0}, /* 7  Left Foot */
1798         {-SX,   0}, /* 8  Right Foot */
1799         {-17, 174}, /* 9  Head1 */
1800         { 17, 140}, /* 10 Head2 */
1801   };
1802   XPoint a[XtNumber(figure)];
1803
1804   /* Translate into device coords */
1805   for(i = 0; i < XtNumber(figure); i++) {
1806         a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1807         a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1808   }
1809
1810   XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1811                 ARMWIDTH, LineSolid, CapRound, JoinRound);
1812   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1813
1814   i = 0; /* Body */
1815   p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1816   p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1817   XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1818                          p, i, CoordModeOrigin);
1819
1820   i = 0;  /* Legs */
1821   p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1822   XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1823                          p, i, CoordModeOrigin);
1824
1825   i = 0;  /* Neck */
1826   p[i++] = a[5]; p[i++] = a[6];
1827   XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1828                          p, i, CoordModeOrigin);
1829
1830   /* Head */
1831   XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1832                    a[9].x, a[9].y,
1833                    a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1834   sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1835   sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1836   if(init) {
1837         /* Initialise arms */
1838         unsigned int i;
1839         for(i = 0; i < 2; i++){
1840           sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1841           sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1842           sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1843           sp->arm[i][LEFT][HAND].x = figure[0].x;
1844           sp->arm[i][LEFT][HAND].y = figure[1].y;
1845           sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1846           sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1847           sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1848           sp->arm[i][RIGHT][HAND].x = figure[4].x;
1849           sp->arm[i][RIGHT][HAND].y = figure[1].y;
1850         }
1851   }
1852 }
1853
1854 static void
1855 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1856 {
1857   jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1858   int offset = (int)(s->angle*64*180/M_PI);
1859   short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1860   short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1861
1862   /* Avoid wrapping */
1863   if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
1864
1865   XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1866   if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi))  {
1867         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1868                          x - BALLRADIUS, y - BALLRADIUS,
1869                          2*BALLRADIUS, 2*BALLRADIUS,
1870                          0, 23040);
1871   } else if (s->divisions == 4) { /* 90 degree divisions */
1872         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1873                          x - BALLRADIUS, y - BALLRADIUS,
1874                          2*BALLRADIUS, 2*BALLRADIUS,
1875                          offset % 23040, 5760);
1876         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1877                          x - BALLRADIUS, y - BALLRADIUS,
1878                          2*BALLRADIUS, 2*BALLRADIUS,
1879                          (offset + 11520) % 23040, 5760);
1880
1881         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1882         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1883                          x - BALLRADIUS, y - BALLRADIUS,
1884                          2*BALLRADIUS, 2*BALLRADIUS,
1885                          (offset + 5760) % 23040, 5760);
1886         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1887                          x - BALLRADIUS, y - BALLRADIUS,
1888                          2*BALLRADIUS, 2*BALLRADIUS,
1889                          (offset + 17280) % 23040, 5760);
1890   } else if (s->divisions == 2)  { /* 180 degree divisions */
1891         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1892                          x - BALLRADIUS, y - BALLRADIUS,
1893                          2*BALLRADIUS, 2*BALLRADIUS,
1894                          offset % 23040, 11520);
1895
1896         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1897         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1898                          x - BALLRADIUS, y - BALLRADIUS,
1899                          2*BALLRADIUS, 2*BALLRADIUS,
1900                          (offset + 11520) % 23040, 11520);
1901   } else {
1902         (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1903                         MI_SCREEN(mi), s->divisions);
1904   }
1905 }
1906
1907 static void
1908 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1909 {
1910         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1911         XPoint p[4];
1912         const double sa = sin(s->angle);
1913         const double ca = cos(s->angle);
1914         unsigned int i;
1915
1916         /*  6   6
1917          +-+
1918         /   \
1919      4 +-----+ 7
1920       ////////\
1921    3 +---------+ 8
1922    2 +---------+ 9
1923       |///////|
1924     1 +-------+ 10
1925        |     |
1926        |     |
1927         |   |
1928         |   |
1929          | |
1930          | |
1931          +-+
1932         0  11   */
1933
1934         static const XPoint club[] = {
1935           {-24, 2}, /* 0 */
1936           {-10, 3}, /* 1 */
1937           {  1, 6}, /* 2 */
1938           {  8, 6}, /* 3 */
1939           { 14, 4}, /* 4 */
1940           { 16, 3}, /* 5 */
1941           { 16,-3}, /* 6 */
1942           { 14,-4}, /* 7 */
1943           {  8,-6}, /* 8 */
1944           {  1,-6}, /* 9 */
1945           {-10,-3}, /* 10 */
1946           {-24,-2}, /* 11 */
1947           {-24, 2}, /* 0 close boundary */
1948         };
1949         XPoint a[XtNumber(club)];
1950
1951         /* Avoid wrapping */
1952         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
1953
1954         /* Translate and fake perspective */
1955         for(i = 0; i < XtNumber(club); i++) {
1956           a[i].x = (short)(MI_WIDTH(mi)/2 +
1957                                            (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1958                                            club[i].y*sqrt(sp->scale)*ca);
1959           a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1960                                            club[i].y*sa*sqrt(sp->scale));
1961         }
1962
1963         if(color != MI_BLACK_PIXEL(mi)) {
1964           /* Outline in black */
1965           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1966           XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1967                                                  LineSolid, CapRound, JoinRound);
1968           XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1969                                  a, XtNumber(a), CoordModeOrigin);
1970         }
1971
1972         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1973
1974         /* Don't be tempted to optimize erase by drawing all the black in
1975            one X operation.  It must use the same ops as the colours to
1976            guarantee a clean erase. */
1977
1978         i = 0; /* Colored stripes */
1979         p[i++] = a[1]; p[i++] = a[2];
1980         p[i++] = a[9]; p[i++] = a[10];
1981         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1982                                  p, i, Convex, CoordModeOrigin);
1983         i = 0;
1984         p[i++] = a[3]; p[i++] = a[4];
1985         p[i++] = a[7]; p[i++] = a[8];
1986         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1987                                  p, i, Convex, CoordModeOrigin);
1988
1989         if(color != MI_BLACK_PIXEL(mi)) {
1990           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1991         }
1992
1993         i = 0; /* White center band */
1994         p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
1995         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1996                                  p, i, Convex, CoordModeOrigin);
1997
1998         i = 0; /* White handle */
1999         p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2000         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2001                                  p, i, Convex, CoordModeOrigin);
2002
2003         i = 0; /* White tip */
2004         p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2005         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2006                                  p, i, Convex, CoordModeOrigin);
2007 }
2008
2009 #if 0
2010 static void
2011 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2012 {
2013         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2014         XPoint p[6];
2015         const double sa = sin(s->angle);
2016         const double ca = cos(s->angle);
2017         unsigned int i;
2018
2019         /*  4   5
2020          +-+
2021         /   \
2022      3 +-----+ 6
2023       ////////\
2024    2 +/////////+ 7
2025       |///////|
2026     1 +-------+ 8
2027        |     |
2028        |     |
2029         |   |
2030         |   |
2031          | |
2032          | |
2033          +-+
2034         0  9    */
2035
2036         static const XPoint club[] = {
2037           {-24, 2}, /* 0 */
2038           { -9, 3}, /* 1 */
2039           {  5, 6}, /* 2 */
2040           { 11, 4}, /* 3 */
2041           { 16, 3}, /* 4 */
2042           { 16,-3}, /* 5 */
2043           { 11,-4}, /* 6 */
2044           {  5,-6}, /* 7 */
2045           { -9,-3}, /* 8 */
2046           {-24,-2}, /* 9 */
2047           {-24, 2}, /* 0 close boundary */
2048         };
2049         XPoint a[XtNumber(club)];
2050
2051         /* Avoid wrapping */
2052         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
2053
2054         /* Translate and fake perspective */
2055         for(i = 0; i < XtNumber(club); i++) {
2056           a[i].x = (short)(MI_WIDTH(mi)/2 +
2057                                            (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2058                                            club[i].y*sqrt(sp->scale)*ca);
2059           a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2060                                            club[i].y*sa*sqrt(sp->scale));
2061         }
2062
2063         if(color != MI_BLACK_PIXEL(mi)) {
2064           /* Outline in black */
2065           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2066           XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2067                                                  LineSolid, CapRound, JoinRound);
2068           XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2069                                  a, XtNumber(a), CoordModeOrigin);
2070         }
2071
2072         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2073
2074         /* Don't be tempted to optimize erase by drawing all the black in
2075            one X operation.  It must use the same ops as the colours to
2076            guarantee a clean erase. */
2077
2078         i = 0; /* Coloured center band */
2079         p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2080         p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2081         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2082                                  p, i, Convex, CoordModeOrigin);
2083
2084         if(color != MI_BLACK_PIXEL(mi)) {
2085           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2086         }
2087
2088         i = 0; /* White handle */
2089         p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2090         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2091                                  p, i, Convex, CoordModeOrigin);
2092
2093         i = 0; /* White tip */
2094         p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2095         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2096                                  p, i, Convex, CoordModeOrigin);
2097 }
2098 #endif
2099
2100 static void
2101 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2102 {
2103         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2104         XPoint head, tail, last;
2105         DXPoint dhead, dlast;
2106         const double sa = sin(s->angle);
2107         const double ca = cos(s->angle);
2108
2109         const double TailLen = -24;
2110         const double HeadLen = 16;
2111         const short Width   = (short)(5 * sqrt(sp->scale));
2112
2113         /*
2114       +///+ head
2115     last  |
2116           |
2117           |
2118           |
2119           |
2120           + tail
2121         */
2122
2123         dhead.x = s->x + HeadLen * PERSPEC * sa;
2124         dhead.y = s->y - HeadLen * ca;
2125
2126         if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2127           dlast = s->dlast;
2128         } else { /* Store 'last' so we can use it later when s->prev has
2129                                 gone */
2130           if(s->prev != s->next) {
2131                 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2132                 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2133           } else {
2134                 dlast = dhead;
2135           }
2136           s->dlast = dlast;
2137         }
2138
2139         /* Avoid wrapping (after last is stored) */
2140         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
2141
2142         head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2143         head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2144
2145         last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2146         last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2147
2148         tail.x = (short)(MI_WIDTH(mi)/2 +
2149                                          (s->x + TailLen * PERSPEC * sa)*sp->scale );
2150         tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2151
2152         if(color != MI_BLACK_PIXEL(mi)) {
2153           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2154           XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2155                                                  Width, LineSolid, CapRound, JoinRound);
2156           XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2157                                 head.x, head.y, tail.x, tail.y);
2158         }
2159         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2160         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2161                                            Width * 2, LineSolid, CapRound, JoinRound);
2162
2163         XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2164                           head.x, head.y, last.x, last.y);
2165
2166 }
2167
2168 static void
2169 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2170 {
2171         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2172         unsigned int i;
2173         const double sa = sin(s->angle);
2174         const double ca = cos(s->angle);
2175
2176         /*
2177         2 +
2178           |+ 3
2179           ||
2180         1 +++ 5
2181           |4|
2182           | |
2183            + 0
2184         */
2185         static const XPoint knife[] = {
2186           {-24, 0}, /* 0 */
2187           { -5,-3}, /* 1 */
2188           { 16,-3}, /* 2 */
2189           { 12, 0}, /* 3 */
2190           { -5, 0}, /* 4 */
2191           { -5, 3}, /* 5 */
2192         };
2193         XPoint a[XtNumber(knife)], p[5];
2194
2195         /* Avoid wrapping */
2196         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
2197
2198         /* Translate and fake perspective */
2199         for(i = 0; i < XtNumber(knife); i++) {
2200           a[i].x = (short)(MI_WIDTH(mi)/2 +
2201                                            (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2202                                            knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2203           a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2204                                            knife[i].y*sa*sqrt(sp->scale));
2205         }
2206
2207         /* Handle */
2208         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2209         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2210                                            LineSolid, CapRound, JoinRound);
2211         XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2212                           a[0].x, a[0].y, a[4].x, a[4].y);
2213
2214         /* Blade */
2215         if(color != MI_BLACK_PIXEL(mi)) {
2216           XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2217         }
2218         i = 0;
2219         p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2220         XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2221                                  p, i, Convex, CoordModeOrigin);
2222 }
2223
2224 static void
2225 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2226 {
2227         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2228         short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2229         short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2230         double radius = 15 * sp->scale;
2231         short thickness = (short)(8 * sqrt(sp->scale));
2232
2233         /* Avoid wrapping */
2234         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
2235
2236         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2237         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2238                                            thickness, LineSolid, CapRound, JoinRound);
2239
2240         XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2241                          (short)(x - radius*PERSPEC), (short)(y - radius),
2242                          (short)(2*radius*PERSPEC), (short)(2*radius),
2243                          0, 23040);
2244 }
2245
2246
2247 static void
2248 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2249 {
2250         jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2251         short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2252         short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2253         double radius = 12 * sp->scale;
2254         int offset = (int)(s->angle*64*180/M_PI);
2255         int holesize = (int)(3.0*sqrt(sp->scale));
2256
2257         /* Avoid wrapping */
2258         if(s->y*sp->scale >  MI_HEIGHT(mi) * 2) return;
2259
2260         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2261         XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2262                          (short)(x - radius), (short)(y - radius),
2263                          (short)(2*radius), (short)(2*radius),
2264                          0, 23040);
2265         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2266         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2267                                            LineSolid, CapRound, JoinRound);
2268         XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2269                          (short)(x - radius), (short)(y - radius),
2270                          (short)(2*radius), (short)(2*radius),
2271                          0, 23040);
2272
2273         /* Draw finger holes */
2274         XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2275                                            LineSolid, CapRound, JoinRound);
2276
2277         XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2278                          (short)(x - radius*0.5), (short)(y - radius*0.5),
2279                          (short)(2*radius*0.5), (short)(2*radius*0.5),
2280                          (offset + 960) % 23040, 0);
2281         XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2282                          (short)(x - radius*0.7), (short)(y - radius*0.7),
2283                          (short)(2*radius*0.7), (short)(2*radius*0.7),
2284                          (offset + 1920) % 23040, 0);
2285         XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2286                          (short)(x - radius*0.7), (short)(y - radius*0.7),
2287                          (short)(2*radius*0.7), (short)(2*radius*0.7),
2288                          offset % 23040, 0);
2289 }
2290
2291 /**************************************************************************
2292  *                    Public Functions                                    *
2293  *                                                                        *
2294  **************************************************************************/
2295
2296
2297 ENTRYPOINT void
2298 release_juggle (ModeInfo * mi)
2299 {
2300   if (juggles != NULL) {
2301         int screen;
2302
2303         for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2304           free_juggle(&juggles[screen]);
2305         free(juggles);
2306         juggles = (jugglestruct *) NULL;
2307   }
2308 }
2309
2310 /* FIXME: refill_juggle currently just appends new throws to the
2311  * programme.  This is fine if the programme is empty, but if there
2312  * are still some trajectories left then it really should take these
2313  * into account */
2314
2315 static void
2316 refill_juggle(ModeInfo * mi)
2317 {
2318   jugglestruct *sp = NULL;
2319   int i;
2320
2321   if (juggles == NULL)
2322         return;
2323   sp = &juggles[MI_SCREEN(mi)];
2324
2325   /* generate pattern */
2326   if (pattern == NULL) {
2327
2328 #define MAXPAT 10
2329 #define MAXREPEAT 300
2330 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2331 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2332
2333         int count = 0;
2334         while (count < MI_CYCLES(mi)) {
2335           char buf[MAXPAT * 3 + 3], *b = buf;
2336           int maxseen = 0;
2337           int l = NRAND(MAXPAT) + 1;
2338           int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2339
2340           { /* vary number of balls */
2341                 int new_balls = sp->num_balls;
2342                 int change;
2343
2344                 if (new_balls == 2) /* Do not juggle 2 that often */
2345                   change = NRAND(2 + CHANGE_BIAS / 4);
2346                 else
2347                   change = NRAND(2 + CHANGE_BIAS);
2348                 switch (change) {
2349                 case 0:
2350                   new_balls++;
2351                   break;
2352                 case 1:
2353                   new_balls--;
2354                   break;
2355                 default:
2356                   break; /* NO-OP */
2357                 }
2358                 if (new_balls < sp->patternindex.minballs) {
2359                   new_balls += 2;
2360                 }
2361                 if (new_balls > sp->patternindex.maxballs) {
2362                   new_balls -= 2;
2363                 }
2364                 if (new_balls < sp->num_balls) {
2365                   if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2366                         return;
2367                 }
2368                 sp->num_balls = new_balls;
2369           }
2370
2371           count += t;
2372           if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2373                 /* Pick from PortFolio */
2374                 int p = sp->patternindex.index[sp->num_balls].start +
2375                   NRAND(sp->patternindex.index[sp->num_balls].number);
2376                 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2377                   return;
2378           } else {
2379                 /* Invent a new pattern */
2380                 *b++='[';
2381                 for(i = 0; i < l; i++){
2382                   int n, m;
2383                   do { /* Triangular Distribution => high values more likely */
2384                         m = NRAND(sp->num_balls + 1);
2385                         n = NRAND(sp->num_balls + 1);
2386                   } while(m >= n);
2387                   if (n == sp->num_balls) {
2388                         maxseen = 1;
2389                   }
2390                   switch(NRAND(5 + POSITION_BIAS)){
2391                   case 0:            /* Outside throw */
2392                         *b++ = '+'; break;
2393                   case 1:            /* Cross throw */
2394                         *b++ = '='; break;
2395                   case 2:            /* Cross catch */
2396                         *b++ = '&'; break;
2397                   case 3:            /* Cross throw and catch */
2398                         *b++ = 'x'; break;
2399                   case 4:            /* Bounce */
2400                         *b++ = '_'; break;
2401                   default:
2402                         break;             /* Inside throw (default) */
2403                   }
2404
2405                   *b++ = n + '0';
2406                   *b++ = ' ';
2407                 }
2408                 *b++ = ']';
2409                 *b = '\0';
2410                 if (maxseen) {
2411                   if (!program(mi, buf, NULL, t))
2412                         return;
2413                 }
2414           }
2415         }
2416   } else { /* pattern supplied in height or 'a' notation */
2417         if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2418           return;
2419   }
2420
2421   adam(sp);
2422
2423   name(sp);
2424
2425   if (!part(sp))
2426         return;
2427
2428   lob(mi);
2429
2430   clap(sp);
2431
2432   positions(sp);
2433
2434   if (!projectile(sp)) {
2435         free_juggle(sp);
2436         return;
2437   }
2438
2439   hands(sp);
2440 #ifdef DEBUG
2441   if(MI_IS_DEBUG(mi)) dump(sp);
2442 #endif
2443 }
2444
2445 static void
2446 change_juggle(ModeInfo * mi)
2447 {
2448   jugglestruct *sp = NULL;
2449   Trajectory *t;
2450
2451   if (juggles == NULL)
2452         return;
2453   sp = &juggles[MI_SCREEN(mi)];
2454
2455   /* Strip pending trajectories */
2456   for (t = sp->head->next; t != sp->head; t = t->next) {
2457         if(t->start > sp->time || t->finish < sp->time) {
2458           Trajectory *n = t;
2459           t=t->prev;
2460           trajectory_destroy(n);
2461         }
2462   }
2463
2464   /* Pick the current object theme */
2465   sp->objtypes = choose_object();
2466
2467   refill_juggle(mi);
2468
2469   /* Clean up the Screen.  Don't use MI_CLEARWINDOW(mi), since we
2470          don't all those special effects. */
2471   XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2472
2473   show_figure(mi, MI_WHITE_PIXEL(mi), True);
2474
2475 }
2476
2477 ENTRYPOINT void
2478 init_juggle (ModeInfo * mi)
2479 {
2480   jugglestruct *sp = 0;
2481   int i;
2482
2483   if (juggles == NULL) { /* First-time initialisation */
2484
2485         /* allocate jugglestruct */
2486         if ((juggles =
2487                  (jugglestruct *)calloc(MI_NUM_SCREENS(mi),
2488                                                                 sizeof (jugglestruct))) == NULL) {
2489           release_juggle(mi);
2490           return;
2491         }
2492   }
2493
2494   sp = &juggles[MI_SCREEN(mi)];
2495
2496   if (only && *only && strcmp(only, " ")) {
2497     balls = clubs = torches = knives = rings = bballs = False;
2498     if (!strcasecmp (only, "balls"))   balls   = True;
2499     else if (!strcasecmp (only, "clubs"))   clubs   = True;
2500     else if (!strcasecmp (only, "torches")) torches = True;
2501     else if (!strcasecmp (only, "knives"))  knives  = True;
2502     else if (!strcasecmp (only, "rings"))   rings   = True;
2503     else if (!strcasecmp (only, "bballs"))  bballs  = True;
2504     else {
2505       (void) fprintf (stderr,
2506                "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2507                "\t rings, or bballs (not \"%s\")\n", only);
2508 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2509       exit (1);
2510 #endif
2511     }
2512   }
2513
2514   if (sp->head == 0) {  /* first time initializing this juggler */
2515
2516         sp->count = ABS(MI_COUNT(mi));
2517         if (sp->count == 0)
2518           sp->count = 200;
2519
2520         /* record start time */
2521         sp->begintime = time(NULL);
2522         if(sp->patternindex.maxballs > 0) {
2523           sp->num_balls = sp->patternindex.minballs +
2524                 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2525         }
2526
2527         show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure.  Also discovers
2528                                                           information about the juggler's
2529                                                           proportions */
2530
2531         /* "7" should be about three times the height of the juggler's
2532            shoulders */
2533         sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2534                                           7 * THROW_CATCH_INTERVAL);
2535
2536         if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2537           balls = True; /* Have to juggle something! */
2538
2539         /* create circular trajectory list */
2540         ADD_ELEMENT(Trajectory, sp->head, sp->head);
2541         if(sp->head == NULL){
2542           free_juggle(sp);
2543           return;
2544         }
2545
2546         /* create circular object list */
2547         ADD_ELEMENT(Object, sp->objects, sp->objects);
2548         if(sp->objects == NULL){
2549           free_juggle(sp);
2550           return;
2551         }
2552
2553         /* create circular wander list */
2554         ADD_ELEMENT(Wander, sp->wander, sp->wander);
2555         if(sp->wander == NULL){
2556           free_juggle(sp);
2557           return;
2558         }
2559         (void)wander(sp, 0); /* Initialize wander */
2560
2561         sp->pattern =  strdup(""); /* Initialise saved pattern with
2562                                                                   free-able memory */
2563   }
2564
2565   sp = &juggles[MI_SCREEN(mi)];
2566
2567   if (pattern &&
2568       (!*pattern ||
2569        !strcasecmp (pattern, ".") ||
2570        !strcasecmp (pattern, "random")))
2571         pattern = NULL;
2572
2573   if (pattern == NULL && sp->patternindex.maxballs == 0) {
2574         /* pattern list needs indexing */
2575         int nelements = XtNumber(portfolio);
2576         int numpat = 0;
2577
2578         /* sort according to number of balls */
2579         qsort((void*)portfolio, nelements,
2580                   sizeof(portfolio[1]), compare_num_balls);
2581
2582         /* last pattern has most balls */
2583         sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2584         /* run through sorted list, indexing start of each group
2585            and number in group */
2586         sp->patternindex.maxballs = 1;
2587         for (i = 0; i < nelements; i++) {
2588           int b = get_num_balls(portfolio[i].pattern);
2589           if (b > sp->patternindex.maxballs) {
2590                 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2591                 if(numpat == 0) sp->patternindex.minballs = b;
2592                 sp->patternindex.maxballs = b;
2593                 numpat = 1;
2594                 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2595           } else {
2596                 numpat++;
2597           }
2598         }
2599         sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2600   }
2601
2602   /* Set up programme */
2603   change_juggle(mi);
2604
2605   /* Clean up the Screen.  Don't use MI_CLEARWINDOW(mi), since we may
2606          only be resizing and then we won't all those special effects. */
2607   XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2608
2609   /* Only put things here that won't interrupt the programme during
2610          a window resize */
2611
2612   /* Use MIN so that users can resize in interesting ways, eg
2613          narrow windows for tall patterns, etc */
2614   sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2615
2616   if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */
2617         sp->mode_font = XQueryFont(MI_DISPLAY(mi), XGContextFromGC(MI_GC(mi)));
2618   }
2619 }
2620
2621 ENTRYPOINT void
2622 reshape_juggle (ModeInfo * mi, int width, int height)
2623 {
2624   init_juggle(mi);
2625 }
2626
2627 ENTRYPOINT void
2628 draw_juggle (ModeInfo * mi)
2629 {
2630   Trajectory *traj = NULL;
2631   Object *o = NULL;
2632   unsigned long future = 0;
2633   jugglestruct *sp = NULL;
2634   char *pattern = NULL;
2635   double cx;
2636
2637   if (juggles == NULL)
2638         return;
2639   sp = &juggles[MI_SCREEN(mi)];
2640
2641   MI_IS_DRAWN(mi) = True;
2642
2643 #ifdef HAVE_COCOA
2644   /* Don't worry about flicker, trust Quartz's double-buffering.
2645      This is a fast fix for the pixel-turds I can't track down...
2646    */
2647   XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2648 #endif
2649
2650   /* Update timer */
2651   if (real) {
2652         struct timeval tv;
2653         (void)gettimeofday(&tv, NULL);
2654         sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2655   } else {
2656         sp->time += MI_DELAY(mi) / 1000;
2657   }
2658
2659   /* First pass: Move arms and strip out expired elements */
2660   for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2661         if (traj->status != PREDICTOR) {
2662           /* Skip any elements that need further processing */
2663           /* We could remove them, but there shoudn't be many and they
2664                  would be needed if we ever got the pattern refiller
2665                  working */
2666           continue;
2667         }
2668         if (traj->start > future) { /* Lookahead to the end of the show */
2669           future = traj->start;
2670         }
2671         if (sp->time < traj->start) { /* early */
2672           continue;
2673         } else if (sp->time < traj->finish) { /* working */
2674
2675           /* Look for pattern name */
2676           if(traj->pattern != NULL) {
2677                 pattern=traj->pattern;
2678           }
2679
2680           if (traj->type == Empty || traj->type == Full) {
2681                 /* Only interested in hands on this pass */
2682                 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2683                 double xd = 0, yd = 0;
2684                 DXPoint p;
2685
2686                 /* Find the catching offset */
2687                 if(traj->object != NULL) {
2688                   if(ObjectDefs[traj->object->type].handle > 0) {
2689                         /* Handles Need to be oriented */
2690                         xd = ObjectDefs[traj->object->type].handle *
2691                           PERSPEC * sin(angle);
2692                         yd = ObjectDefs[traj->object->type].handle *
2693                           cos(angle);
2694                   } else {
2695                         /* Balls are always caught at the bottom */
2696                         xd = 0;
2697                         yd = -4;
2698                   }
2699                 }
2700                 p.x = (CUBIC(traj->xp, sp->time) - xd);
2701                 p.y = (CUBIC(traj->yp, sp->time) + yd);
2702                 reach_arm(mi, traj->hand, &p);
2703
2704                 /* Store updated hand position */
2705                 traj->x = p.x + xd;
2706                 traj->y = p.y - yd;
2707           }
2708           if (traj->type == Ball || traj->type == Full) {
2709                 /* Only interested in objects on this pass */
2710                 double x, y;
2711                 Trace *s;
2712
2713                 if(traj->type == Full) {
2714                   /* Adjusted these in the first pass */
2715                   x = traj->x;
2716                   y = traj->y;
2717                 } else {
2718                   x = CUBIC(traj->xp, sp->time);
2719                   y = CUBIC(traj->yp, sp->time);
2720                 }
2721
2722                 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2723                 s->x = x;
2724                 s->y = y;
2725                 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2726                 s->divisions = traj->divisions;
2727                 traj->object->tracelen++;
2728                 traj->object->active = True;
2729           }
2730         } else { /* expired */
2731           Trajectory *n = traj;
2732           traj=traj->prev;
2733           trajectory_destroy(n);
2734         }
2735   }
2736
2737   /* Erase end of trails */
2738   for (o = sp->objects->next; o != sp->objects; o = o->next) {
2739         Trace *s;
2740         for (s = o->trace->next;
2741                  o->trace->next != o->trace &&
2742                    (o->count == 0 || o->tracelen > o->tail);
2743                  s = o->trace->next) {
2744           ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2745           REMOVE(s);
2746           o->tracelen--;
2747           if(o->count <= 0 && o->tracelen <= 0) {
2748                 /* Object no longer in use and trail gone */
2749                 Object *n = o;
2750                 o = o->prev;
2751                 object_destroy(n);
2752           }
2753           if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2754         }
2755   }
2756
2757   show_arms(mi, MI_BLACK_PIXEL(mi));
2758   cx = wander(sp, sp->time);
2759   /* Reduce flicker by only permitting movements of more than a pixel */
2760   if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2761         show_figure(mi, MI_BLACK_PIXEL(mi), False);
2762         sp->cx = cx;
2763   }
2764
2765   show_figure(mi, MI_WHITE_PIXEL(mi), False);
2766
2767   show_arms(mi, MI_WHITE_PIXEL(mi));
2768
2769   /* Draw Objects */
2770   for (o = sp->objects->next; o != sp->objects; o = o->next) {
2771         if(o->active) {
2772           ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2773           o->active = False;
2774         }
2775   }
2776
2777
2778   /* Save pattern name so we can erase it when it changes */
2779   if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2780         /* Erase old name */
2781         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2782 # if 0
2783         XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2784                                 0, 20, sp->pattern, strlen(sp->pattern));
2785 # else
2786     XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2787                    0, 0, MI_WIDTH(mi), 25);
2788 # endif
2789         free(sp->pattern);
2790         sp->pattern = strdup(pattern);
2791
2792         if (MI_IS_VERBOSE(mi)) {
2793           (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2794                                          MI_SCREEN(mi), sp->pattern);
2795         }
2796   }
2797   if(sp->mode_font != None &&
2798          XTextWidth(sp->mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2799         /* Redraw once a cycle, in case it's obscured or it changed */
2800         XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2801         XDrawImageString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2802                      0, 20, sp->pattern, strlen(sp->pattern));
2803   }
2804
2805 #ifdef MEMTEST
2806   if((int)(sp->time/10) % 1000 == 0)
2807         (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2808 #endif
2809
2810   if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2811         refill_juggle(mi);
2812   } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2813         release_juggle(mi);
2814         init_juggle(mi);
2815   }
2816 }
2817
2818 XSCREENSAVER_MODULE ("Juggle", juggle)
2819
2820 #endif /* MODE_juggle */