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