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