1 /* -*- Mode: C; tab-width: 4 -*- */
5 static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore";
9 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
11 * Permission to use, copy, modify, and distribute this software and its
12 * documentation for any purpose and without fee is hereby granted,
13 * provided that the above copyright notice appear in all copies and that
14 * both that copyright notice and this permission notice appear in
15 * supporting documentation.
17 * This file is provided AS IS with no warranties of any kind. The author
18 * shall have no liability with respect to the infringement of copyrights,
19 * trade secrets or any patents by this file or any part thereof. In no
20 * event will the author be liable for any lost revenue or profits or
21 * other special, indirect and consequential damages.
24 * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
25 * Add -rings, -bballs. Add -describe. Finally made
26 * live pattern updates possible. Add refill_juggle(),
27 * change_juggle() and reshape_juggle(). Make
28 * init_juggle() non-destructive. Reorder erase/draw
29 * operations. Update xscreensaver xml and manpage.
30 * 15-Nov-2004: [TDA] Fix all memory leaks.
31 * 12-Nov-2004: [TDA] Add -torches and another new trail
32 * implementation, so that different objects can have
33 * different length trails.
34 * 11-Nov-2004: [TDA] Clap when all the balls are in the air.
35 * 10-Nov-2004: [TDA] Display pattern name converted to hight
37 * 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
38 * 02-Sep-2003: Non-real time to see what is happening without a
39 * strobe effect for slow machines.
40 * 01-Nov-2000: Allocation checks
46 * Implement the anonymously promised -uni option.
51 * Notes on Adam Chalcraft Juggling Notation (used by permission)
52 * a-> Adam's notation s-> Site swap (Cambridge) notation
54 * To define a map from a-notation to s-notation ("site-swap"), both
55 * of which look like doubly infinite sequences of natural numbers. In
56 * s-notation, there is a restriction on what is allowed, namely for
57 * the sequence s_n, the associated function f(n)=n+s_n must be a
58 * bijection. In a-notation, there is no restriction.
60 * To go from a-notation to s-notation, you start by mapping each a_n
61 * to a permutation of N, the natural numbers.
64 * 1 -> (10) [i.e. f(1)=0, f(0)=1]
65 * 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
66 * 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
69 * Then for each n, you look at how long 0 takes to get back to 0
70 * again and you call this t_n. If a_n=0, for example, then since the
71 * identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
72 * a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
73 * next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
74 * following the 1. Finally, set s_n = t_n - 1.
76 * To give some examples, it helps to have a notation for cyclic
77 * sequences. By (123), for example, I mean ...123123123123... . Now
78 * under the a-notation -> s-notation mapping we have some familiar
81 * (0)->(0), (1)->(1), (2)->(2) etc.
82 * (21)->(31), (31)->(51), (41)->(71) etc.
83 * (10)->(20), (20)->(40), (30)->(60) etc.
84 * (331)->(441), (312)->(612), (303)->(504), (321)->(531)
85 * (43)->(53), (434)->(534), (433)->(633)
88 * In general, the number of balls is the *average* of the s-notation,
89 * and the *maximum* of the a-notation. Another theorem is that the
90 * minimum values in the a-notation and the s-notation and equal, and
91 * preserved in the same positions.
93 * The usefulness of a-notation is the fact that there are no
94 * restrictions on what is allowed. This makes random juggle
95 * generation much easier. It also makes enumeration very
96 * easy. Another handy feature is computing changes. Suppose you can
97 * do (5) and want a neat change up to (771) in s-notation [Mike Day
98 * actually needed this example!]. Write them both in a-notation,
99 * which gives (5) and (551). Now concatenate them (in general, there
100 * may be more than one way to do this, but not in this example), to
103 * ...55555555551551551551551...
105 * Now convert back to s-notation, to get
107 * ...55555566771771771771771...
109 * So the answer is to do two 6 throws and then go straight into
110 * (771). Coming back down of course,
112 * ...5515515515515515555555555...
116 * ...7717717717716615555555555...
118 * so the answer is to do a single 661 and then drop straight down to
121 * [The number of balls in the generated pattern occasionally changes.
122 * In order to decrease the number of balls I had to introduce a new
123 * symbol into the Adam notation, [*] which means 'lose the current
127 /* This code uses so many linked lists it's worth having a built-in
133 # define DEFAULTS "*delay: 10000 \n" \
137 "*font: -*-helvetica-bold-r-normal-*-180-*\n" \
138 "*fpsSolid: true\n" \
140 # define release_juggle 0
141 # define reshape_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 */
152 #define XClearWindow(d, w) \
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)); \
160 #define DEF_PATTERN "random" /* All patterns */
161 #define DEF_TAIL "1" /* No trace */
163 /* Maybe a ROLA BOLA would be at a better angle for viewing */
164 #define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
166 #define DEF_REAL "True"
167 #define DEF_DESCRIBE "True"
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 */
177 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
180 static char *pattern;
186 static Bool describe;
195 static XrmOptionDescRec opts[] =
197 {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
198 {"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
200 {"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
201 {"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
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 },
221 static argtype vars[] =
223 { &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
224 { &tail, "tail", "Tail", DEF_TAIL, t_Int },
226 { &uni, "uni", "Uni", DEF_UNI, t_Bool },
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 },
238 static OptionStruct desc[] =
240 { "-pattern string", "Cambridge Juggling Pattern" },
241 { "-tail num", "Trace Juggling Patterns" },
243 { "-/+uni", "Unicycle" },
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." },
256 ENTRYPOINT ModeSpecOpt juggle_opts =
257 { XtNumber(opts), opts, XtNumber(vars), vars, desc };
260 ModStruct juggle_description = {
261 "juggle", "init_juggle", "draw_juggle", (char *) NULL,
262 "draw_juggle", "change_juggle", "free_juggle", &juggle_opts,
263 10000, 200, 1000, 1, 64, 1.0, "",
264 "Shows a Juggler, juggling", 0, NULL
270 # include <X11/unix_time.h>
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 */
281 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
283 #define BALLRADIUS ARMWIDTH
288 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
290 /* Timing based on count. Units are milliseconds. Juggles per second
291 is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
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)
297 /********************************************************************
298 * Trace Definitions *
300 * These record rendering data so that a drawn object can be erased *
301 * later. Each object has its own Trace list. *
303 ********************************************************************/
305 typedef struct {double x, y; } DXPoint;
306 typedef struct trace *TracePtr;
307 typedef struct trace {
318 /*******************************************************************
319 * Object Definitions *
321 * These describe the various types of Object that can be juggled *
323 *******************************************************************/
324 typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
326 static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
327 static DrawProc show_ring, show_bball;
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 */
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 */
352 0.55, /* Clubs don't bounce too well */
358 20, /* Torches need flames */
359 0, /* Torches don't bounce -- fire risk! */
366 0, /* Knives don't bounce */
385 /**************************
386 * Trajectory definitions *
387 **************************/
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];
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;
405 unsigned long finish;
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;
422 int count; /* reference count */
423 Bool active; /* Object is in use */
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. */
438 typedef struct trajectory *TrajectoryPtr;
439 typedef struct trajectory {
440 TrajectoryPtr prev, next; /* for building list */
441 TrajectoryStatus status;
459 TrajectoryPtr balllink;
460 TrajectoryPtr handlink;
463 double cx; /* Moving juggler */
464 double x, y; /* current position */
465 double dx, dy; /* initial velocity */
469 unsigned long start, finish;
483 const char * pattern;
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,
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 */},
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 */},
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 */},
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 */},
561 {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
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 */},
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"},
580 typedef struct { int start; int number; } PatternIndex;
582 struct patternindex {
585 PatternIndex index[XtNumber(portfolio)];
589 /* Jugglestruct: per-screen global data. The master Wander, Object
590 * and Trajectory lists are anchored here. */
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*/
606 struct patternindex patternindex;
607 XFontStruct *mode_font;
610 static jugglestruct *juggles = (jugglestruct *) NULL;
616 #define DUP_OBJECT(n, t) { \
617 (n)->object = (t)->object; \
618 if((n)->object != NULL) (n)->object->count++; \
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; \
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; \
637 (t)->next->prev = (t); \
641 object_destroy(Object* o)
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' */
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);
661 REMOVE(t); /* Unlink and free */
665 free_juggle(ModeInfo * mi) {
666 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
668 if (sp->head != NULL) {
669 while (sp->head->next != sp->head) {
670 trajectory_destroy(sp->head->next);
673 sp->head = (Trajectory *) NULL;
675 if(sp->objects != NULL) {
676 while (sp->objects->next != sp->objects) {
677 object_destroy(sp->objects->next);
680 sp->objects = (Object*)NULL;
682 if(sp->wander != NULL) {
683 while (sp->wander->next != sp->wander) {
684 Wander *w = sp->wander->next;
688 sp->wander = (Wander*)NULL;
690 if(sp->pattern != NULL) {
694 if (sp->mode_font!=None) {
695 XFreeFontInfo(NULL,sp->mode_font,1);
696 sp->mode_font = None;
701 add_throw(ModeInfo *mi, char type, int h, Notation n, const char* name)
703 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
706 ADD_ELEMENT(Trajectory, t, sp->head->prev);
707 if(t == NULL){ /* Out of Memory */
713 t->name = strdup(name);
726 /* add a Thratch to the performance */
728 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
735 if (MI_IS_VERBOSE(mi)) {
736 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
737 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
740 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
741 have been programmed */
742 /* title is the pattern name to be supplied to the first throw of
743 a sequence. If no name if given, use an empty title so that
744 the sequences are still delimited. */
745 const char *title = (name != NULL)? name : "";
750 for(p=patn; *p; p++) {
751 if (*p >= '0' && *p <='9') {
753 h = 10*h + (*p - '0');
755 Notation nn = notation;
757 case '[': /* begin Adam notation */
760 case '-': /* Inside throw */
763 case '+': /* Outside throw */
764 case '=': /* Cross throw */
765 case '&': /* Cross catch */
766 case 'x': /* Cross throw and catch */
767 case '_': /* Bounce */
768 case 'k': /* Kickup */
771 case '*': /* Lose ball */
775 case ']': /* end Adam notation */
781 if (!add_throw(mi, type, h, notation, title))
791 if(w == 0) { /* Only warn on first pass */
792 (void) fprintf(stderr,
793 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
800 if (seen) { /* end of sequence */
801 if (!add_throw(mi, type, h, notation, title))
815 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
817 4 4 1 3 12 2 4 1 4 4 13 0 3 1
820 #define BOUNCEOVER 10
824 /* Convert Adam notation into heights */
826 adam(jugglestruct *sp)
829 for(t = sp->head->next; t != sp->head; t = t->next) {
830 if (t->status == ATCH) {
833 for(p = t->next; a > 0; p = p->next) {
835 t->height = -9; /* Indicate end of processing for name() */
838 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
843 if(t->height > BOUNCEOVER && t->posn == ' '){
844 t->posn = '_'; /* high defaults can be bounced */
845 } else if(t->height < 3 && t->posn == '_') {
846 t->posn = ' '; /* Can't bounce short throws. */
848 if(t->height < KICKMIN && t->posn == 'k'){
849 t->posn = ' '; /* Can't kick short throws */
851 if(t->height > THROWMAX){
852 t->posn = 'k'; /* Use kicks for ridiculously high throws */
859 /* Discover converted heights and update the sequence title */
861 name(jugglestruct *sp)
866 for(t = sp->head->next; t != sp->head; t = t->next) {
867 if (t->status == THRATCH && t->name != NULL) {
869 for(p = t; p == t || p->name == NULL; p = p->next) {
870 if(p == sp->head || p->height < 0) { /* end of reliable data */
874 b += sprintf(b, " %d", p->height);
876 b += sprintf(b, " %c%d", p->posn, p->height);
878 if(b - buffer > 500) break; /* otherwise this could eventually
879 overflow. It'll be too big to
883 (void) sprintf(b, ", %s", t->name);
885 free(t->name); /* Don't need name any more, it's been converted
888 if(t->pattern != NULL) free(t->pattern);
889 t->pattern = strdup(buffer);
894 /* Split Thratch notation into explicit throws and catches.
895 Usually Catch follows Throw in same hand, but take care of special
898 /* ..n1.. -> .. LTn RT1 LC RC .. */
899 /* ..nm.. -> .. LTn LC RTm RC .. */
904 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
905 Trajectory *t, *nt, *p;
906 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
908 for (t = sp->head->next; t != sp->head; t = t->next) {
909 if (t->status > THRATCH) {
911 } else if (t->status == THRATCH) {
914 /* plausibility check */
915 if (t->height <= 2 && t->posn == '_') {
916 t->posn = ' '; /* no short bounces */
918 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
919 t->posn = ' '; /* 1's need close catches */
924 case ' ': posn = '-'; t->posn = '+'; break;
925 case '+': posn = '+'; t->posn = '-'; break;
926 case '=': posn = '='; t->posn = '+'; break;
927 case '&': posn = '+'; t->posn = '='; break;
928 case 'x': posn = '='; t->posn = '='; break;
929 case '_': posn = '_'; t->posn = '-'; break;
930 case 'k': posn = 'k'; t->posn = 'k'; break;
932 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
935 hand = (Hand) ((hand + 1) % 2);
940 if (t->height == 1 && p != sp->head) {
941 p = p->prev; /* '1's are thrown earlier than usual */
947 ADD_ELEMENT(Trajectory, nt, p);
955 nt->height = t->height;
965 choose_object(void) {
968 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
969 if(balls && o == BALL) break;
970 if(clubs && o == CLUB) break;
971 if(torches && o == TORCH) break;
972 if(knives && o == KNIFE) break;
973 if(rings && o == RING) break;
974 if(bballs && o == BBALLS) break;
979 /* Connnect up throws and catches to figure out which ball goes where.
980 Do the same with the juggler's hands. */
985 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
988 for (t = sp->head->next; t != sp->head; t = t->next) {
989 if (t->status == ACTION) {
990 if (t->action == THROW) {
991 if (t->type == Empty) {
992 /* Create new Object */
993 ADD_ELEMENT(Object, t->object, sp->objects);
994 t->object->count = 1;
995 t->object->tracelen = 0;
996 t->object->active = False;
997 /* Initialise object's circular trace list */
998 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
1000 if (MI_NPIXELS(mi) > 2) {
1001 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
1004 t->object->color = 1;
1006 t->object->color = 0;
1010 /* Small chance of picking a random object instead of the
1012 if(NRAND(OBJMIXPROB) == 0) {
1013 t->object->type = choose_object();
1015 t->object->type = sp->objtypes;
1018 /* Check to see if we need trails for this object */
1019 if(tail < ObjectDefs[t->object->type].mintrail) {
1020 t->object->tail = ObjectDefs[t->object->type].mintrail;
1022 t->object->tail = tail;
1026 /* Balls can change divisions at each throw */
1027 t->divisions = 2 * (NRAND(2) + 1);
1029 /* search forward for next catch in this hand */
1030 for (p = t->next; t->handlink == NULL; p = p->next) {
1031 if(p->status < ACTION || p == sp->head) return;
1032 if (p->action == CATCH) {
1033 if (t->handlink == NULL && p->hand == t->hand) {
1039 if (t->height > 0) {
1042 /* search forward for next ball catch */
1043 for (p = t->next; t->balllink == NULL; p = p->next) {
1044 if(p->status < ACTION || p == sp->head) {
1048 if (p->action == CATCH) {
1049 if (t->balllink == NULL && --h < 1) { /* caught */
1050 t->balllink = p; /* complete trajectory */
1052 if (p->type == Full) {
1053 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1054 MI_SCREEN(mi), t->object->color);
1058 DUP_OBJECT(p, t); /* accept catch */
1059 p->angle = t->angle;
1060 p->divisions = t->divisions;
1065 t->type = Empty; /* thrown */
1066 } else if (t->action == CATCH) {
1067 /* search forward for next throw from this hand */
1068 for (p = t->next; t->handlink == NULL; p = p->next) {
1069 if(p->status < ACTION || p == sp->head) return;
1070 if (p->action == THROW && p->hand == t->hand) {
1071 p->type = t->type; /* pass ball */
1072 DUP_OBJECT(p, t); /* pass object */
1073 p->divisions = t->divisions;
1078 t->status = LINKEDACTION;
1083 /* Clap when both hands are empty */
1085 clap(jugglestruct *sp)
1088 for (t = sp->head->next; t != sp->head; t = t->next) {
1089 if (t->status == LINKEDACTION &&
1090 t->action == CATCH &&
1092 t->handlink != NULL &&
1093 t->handlink->height == 0) { /* Completely idle hand */
1095 for (p = t->next; p != sp->head; p = p->next) {
1096 if (p->status == LINKEDACTION &&
1097 p->action == CATCH &&
1098 p->hand != t->hand) { /* Next catch other hand */
1099 if(p->type == Empty &&
1100 p->handlink != NULL &&
1101 p->handlink->height == 0) { /* Also completely idle */
1103 t->handlink->posn = '^'; /* Move first hand's empty throw */
1104 p->posn = '^'; /* to meet second hand's empty
1108 break; /* Only need first catch */
1115 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1117 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1118 with velocity dx1 at time t1 */
1120 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1129 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1130 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1135 s.c = (3*a*t0 - 2*b)*t0 + c;
1136 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1140 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1141 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1142 velocity dx2 at time t2. The arrival and departure velocities at
1143 x1, t1 must be the same. */
1145 makeSplinePair(Spline *s1, Spline *s2,
1146 double x0, double dx0, int t0,
1148 double x2, double dx2, int t2)
1150 double x10, x21, t21, t10, t20, dx1;
1156 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1157 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1159 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1160 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1164 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1165 from x at time t at constant velocity dx. sy goes from y at time t
1166 with velocity dy and constant acceleration g. */
1168 makeParabola(Trajectory *n,
1169 double x, double dx, double y, double dy, double g)
1171 double t = (double)n->start;
1175 n->xp.d = -dx*t + x;
1178 n->yp.c = -g*t + dy;
1179 n->yp.d = g/2*t*t - dy*t + y;
1184 /* Make juggler wander around the screen */
1185 static double wander(jugglestruct *sp, unsigned long time)
1188 for (w = sp->wander->next; w != sp->wander; w = w->next) {
1189 if (w->finish < sp->time) { /* expired */
1193 } else if(w->finish > time) {
1197 if(w == sp->wander) { /* Need a new one */
1198 ADD_ELEMENT(Wander, w, sp->wander->prev);
1199 if(w == NULL) { /* Memory problem */
1202 w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1206 w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1208 w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1210 return CUBIC(w->s, time);
1213 #define SX 25 /* Shoulder Width */
1215 /* Convert hand position symbols into actual time/space coordinates */
1217 positions(jugglestruct *sp)
1220 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1221 for (t = sp->head->next; t != sp->head; t = t->next) {
1222 if (t->status >= PTHRATCH) {
1224 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1225 /* Allow ACTIONs to be annotated, but we won't mark them ready
1226 for the next stage */
1233 if (t->action == CATCH) { /* Throw-to-catch */
1234 if (t->type == Empty) {
1235 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1236 } else { /* successful catch */
1237 now += (int)(THROW_CATCH_INTERVAL);
1239 } else { /* Catch-to-throw */
1240 if(t->object != NULL) {
1241 now += (int) (CATCH_THROW_INTERVAL *
1242 ObjectDefs[t->object->type].weight);
1244 now += (int) (CATCH_THROW_INTERVAL);
1250 else /* Concatenated performances may need clock resync */
1253 t->cx = wander(sp, t->start);
1258 /* Add room for the handle */
1259 if(t->action == CATCH && t->object != NULL)
1260 yo -= ObjectDefs[t->object->type].handle;
1263 case '-': xo = sx - pose; break;
1266 case '+': xo = sx + pose; break;
1268 case '=': xo = - sx - pose; yo += pose; break;
1269 case '^': xo = 0; yo += pose*2; break; /* clap */
1271 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1275 t->angle = (((t->hand == LEFT) ^
1276 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1279 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1282 /* Only mark complete if it was already linked */
1283 if(t->status == LINKEDACTION) {
1284 t->status = PTHRATCH;
1291 /* Private physics functions */
1293 /* Compute the spin-rate for a trajectory. Different types of throw
1294 (eg, regular thows, bounces, kicks, etc) have different spin
1297 type = type of object
1298 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1299 old = earlier spin to consider
1300 dt = time span of this trajectory
1301 height = height of ball throw or 0 if based on old spin
1302 turns = full club turns required during this operation
1303 togo = partial club turns required to match hands
1306 spinrate(ObjType type, Trajectory *h, double old, double dt,
1307 int height, int turns, double togo)
1309 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1311 if(ObjectDefs[type].handle != 0) { /* Clubs */
1312 return (dir * turns * 2 * M_PI + togo) / dt;
1313 } else if(height == 0) { /* Balls already spinning */
1315 } else { /* Balls */
1316 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1321 /* compute the angle at the end of a spinning trajectory */
1323 end_spin(Trajectory *t)
1325 return t->angle + t->spin * (t->finish - t->start);
1328 /* Sets the initial angle of the catch following hand movement t to
1329 the final angle of the throw n. Also sets the angle of the
1330 subsequent throw to the same angle plus half a turn. */
1332 match_spins_on_catch(Trajectory *t, Trajectory *n)
1334 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1335 t->balllink->angle = end_spin(n);
1336 if(t->balllink->handlink != NULL) {
1337 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1343 find_bounce(jugglestruct *sp,
1344 double yo, double yf, double yc, double tc, double cor)
1346 double tb, i, dy = 0;
1347 const double e = 1; /* permissible error in yc */
1351 yt = height at catch time after one bounce
1352 one or three roots according to timing
1353 find one by interval bisection
1356 for(i = tc / 2; i > 0.0001; i/=2){
1359 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1362 dy = (yf - yo)/tb + sp->Gr/2*tb;
1364 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1367 }else if(yt > yc - e){
1373 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1380 new_predictor(const Trajectory *t, int start, int finish, double angle)
1383 ADD_ELEMENT(Trajectory, n, t->prev);
1388 n->divisions = t->divisions;
1390 n->status = PREDICTOR;
1398 /* Turn abstract timings into physically appropriate object trajectories. */
1400 projectile(jugglestruct *sp)
1403 const int yf = 0; /* Floor height */
1405 for (t = sp->head->next; t != sp->head; t = t->next) {
1406 if (t->status != PTHRATCH || t->action != THROW) {
1408 } else if (t->balllink == NULL) { /* Zero Throw */
1409 t->status = BPREDICTOR;
1410 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1412 } else if(t->balllink == t->handlink) {
1413 /* '2' height - hold on to ball. Don't need to consider
1414 flourishes, 'hands' will do that automatically anyway */
1417 /* Zero spin to avoid wrist injuries */
1419 match_spins_on_catch(t, t);
1421 t->status = BPREDICTOR;
1424 if (t->posn == '_') { /* Bounce once */
1426 const int tb = t->start +
1427 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1428 (double) (t->balllink->start - t->start),
1429 ObjectDefs[t->object->type].cor);
1431 if(tb < t->start) { /* bounce too hard */
1432 t->posn = '+'; /* Use regular throw */
1434 Trajectory *n; /* First (throw) trajectory. */
1435 double dt; /* Time span of a trajectory */
1436 double dy; /* Distance span of a follow-on trajectory.
1437 First trajectory uses t->dy */
1438 /* dx is constant across both trajectories */
1439 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1441 { /* ball follows parabola down */
1442 n = new_predictor(t, t->start, tb, t->angle);
1443 if(n == NULL) return False;
1444 dt = n->finish - n->start;
1445 /* Ball rate 4, no flight or matching club turns */
1446 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1447 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1448 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1451 { /* ball follows parabola up */
1452 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1454 if(m == NULL) return False;
1455 dt = m->finish - m->start;
1456 /* Use previous ball rate, no flight club turns */
1457 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1458 t->balllink->angle - m->angle);
1459 match_spins_on_catch(t, m);
1460 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1461 makeParabola(m, t->balllink->x - t->dx * dt,
1462 t->dx, (double) yf, dy, sp->Gr);
1465 t->status = BPREDICTOR;
1468 } else if (t->posn == 'k') { /* Drop & Kick */
1469 Trajectory *n; /* First (drop) trajectory. */
1470 Trajectory *o; /* Second (rest) trajectory */
1471 Trajectory *m; /* Third (kick) trajectory */
1472 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1473 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1476 { /* Fall to ground */
1477 n = new_predictor(t, t->start, td, t->angle);
1478 if(n == NULL) return False;
1479 dt = n->finish - n->start;
1480 /* Ball spin rate 4, no flight club turns */
1481 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1482 t->balllink->angle - n->angle);
1483 t->dx = (t->balllink->x - t->x) / dt;
1484 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1485 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1488 { /* Rest on ground */
1489 o = new_predictor(t, n->finish, tk, end_spin(n));
1490 if(o == NULL) return False;
1492 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1497 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1498 if(m == NULL) return False;
1499 dt = m->finish - m->start;
1500 /* Match receiving hand, ball rate 4, one flight club turn */
1501 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1502 4, 1, t->balllink->angle - m->angle);
1503 match_spins_on_catch(t, m);
1504 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1505 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1508 t->status = BPREDICTOR;
1512 /* Regular flight, no bounce */
1513 { /* ball follows parabola */
1515 Trajectory *n = new_predictor(t, t->start,
1516 t->balllink->start, t->angle);
1517 if(n == NULL) return False;
1518 dt = t->balllink->start - t->start;
1520 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1521 t->balllink->angle - n->angle);
1522 match_spins_on_catch(t, n);
1523 t->dx = (t->balllink->x - t->x) / dt;
1524 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1525 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1528 t->status = BPREDICTOR;
1534 /* Turn abstract hand motions into cubic splines. */
1536 hands(jugglestruct *sp)
1538 Trajectory *t, *u, *v;
1540 for (t = sp->head->next; t != sp->head; t = t->next) {
1541 /* no throw => no velocity */
1542 if (t->status != BPREDICTOR) {
1547 if (u == NULL) { /* no next catch */
1551 if (v == NULL) { /* no next throw */
1555 /* double spline takes hand from throw, thru catch, to
1558 t->finish = u->start;
1559 t->status = PREDICTOR;
1561 u->finish = v->start;
1562 u->status = PREDICTOR;
1565 /* FIXME: These adjustments leave a small glitch when alternating
1566 balls and clubs. Just hope no-one notices. :-) */
1568 /* make sure empty hand spin matches the thrown object in case it
1571 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1572 fabs((u->angle - t->angle)/(u->start - t->start));
1574 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1575 fabs((v->angle - u->angle)/(v->start - u->start));
1577 (void) makeSplinePair(&t->xp, &u->xp,
1578 t->x, t->dx, t->start,
1580 v->x, v->dx, v->start);
1581 (void) makeSplinePair(&t->yp, &u->yp,
1582 t->y, t->dy, t->start,
1584 v->y, v->dy, v->start);
1586 t->status = PREDICTOR;
1590 /* Given target x, y find_elbow puts hand at target if possible,
1591 * otherwise makes hand point to the target */
1593 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1597 double x = p->x - s->x;
1598 double y = p->y - s->y;
1599 h2 = x*x + y*y + z*z;
1600 if (h2 > 4 * armlength * armlength) {
1601 t = armlength/sqrt(h2);
1604 h->x = 2 * t * x + s->x;
1605 h->y = 2 * t * y + s->y;
1607 r = sqrt((double)(x*x + z*z));
1608 t = sqrt(4 * armlength * armlength / h2 - 1);
1609 e->x = x*(1 + y*t/r)/2 + s->x;
1610 e->y = (y - r*t)/2 + s->y;
1617 /* NOTE: returned x, y adjusted for arm reach */
1619 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1621 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1623 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1624 *p = sp->arm[1][side][HAND] = h;
1625 sp->arm[1][side][ELBOW] = e;
1629 /* dumps a human-readable rendition of the current state of the juggle
1630 pipeline to stderr for debugging */
1632 dump(jugglestruct *sp)
1635 for (t = sp->head->next; t != sp->head; t = t->next) {
1636 switch (t->status) {
1638 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1641 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1642 t->pattern == NULL?"":t->pattern);
1645 if (t->action == CATCH)
1646 (void) fprintf(stderr, "%p A %c%cC\n",
1648 t->hand ? 'R' : 'L');
1650 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1652 t->hand ? 'R' : 'L',
1653 (t->action == THROW)?'T':'N',
1657 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1660 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1661 t->height, t->object == NULL?0:t->object->color,
1662 (void*)t->handlink, (void*)t->balllink);
1665 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1668 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1669 t->height, t->type, t->object == NULL?0:t->object->color,
1670 t->start, t->finish);
1673 (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1674 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1675 t->object == NULL?0:t->object->color,
1676 t->start, t->finish, t->yp.c);
1679 (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1680 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1681 t->object == NULL?0:t->object->color,
1682 t->start, t->finish, t->yp.c);
1685 (void) fprintf(stderr, "%p: status %d not implemented\n",
1686 (void*)t, t->status);
1690 (void) fprintf(stderr, "---\n");
1694 static int get_num_balls(const char *j)
1700 for (p = j; *p; p++) {
1701 if (*p >= '0' && *p <='9') { /* digit */
1702 h = 10*h + (*p - '0');
1718 compare_num_balls(const void *p1, const void *p2)
1721 i = get_num_balls(((patternstruct*)p1)->pattern);
1722 j = get_num_balls(((patternstruct*)p2)->pattern);
1737 /**************************************************************************
1738 * Rendering Functions *
1740 **************************************************************************/
1743 show_arms(ModeInfo * mi, unsigned long color)
1745 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1748 XPoint a[XtNumber(sp->arm[0][0])];
1749 if(color == MI_BLACK_PIXEL(mi)) {
1754 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1755 ARMWIDTH, LineSolid, CapRound, JoinRound);
1756 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1757 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1758 /* Translate into device coords */
1759 for(i = 0; i < XtNumber(a); i++) {
1760 a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1761 a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
1763 sp->arm[0][side][i] = sp->arm[1][side][i];
1765 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1766 a, XtNumber(a), CoordModeOrigin);
1771 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1773 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1794 static const XPoint figure[] = {
1795 { 15, 70}, /* 0 Left Hip */
1796 { 0, 90}, /* 1 Waist */
1797 { SX, 130}, /* 2 Left Shoulder */
1798 {-SX, 130}, /* 3 Right Shoulder */
1799 {-15, 70}, /* 4 Right Hip */
1800 { 0, 130}, /* 5 Neck */
1801 { 0, 140}, /* 6 Chin */
1802 { SX, 0}, /* 7 Left Foot */
1803 {-SX, 0}, /* 8 Right Foot */
1804 {-17, 174}, /* 9 Head1 */
1805 { 17, 140}, /* 10 Head2 */
1807 XPoint a[XtNumber(figure)];
1809 /* Translate into device coords */
1810 for(i = 0; i < XtNumber(figure); i++) {
1811 a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1812 a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1815 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1816 ARMWIDTH, LineSolid, CapRound, JoinRound);
1817 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1820 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1821 p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1822 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1823 p, i, CoordModeOrigin);
1826 p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1827 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1828 p, i, CoordModeOrigin);
1831 p[i++] = a[5]; p[i++] = a[6];
1832 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1833 p, i, CoordModeOrigin);
1836 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1838 a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1839 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1840 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1842 /* Initialise arms */
1844 for(i = 0; i < 2; i++){
1845 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1846 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1847 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1848 sp->arm[i][LEFT][HAND].x = figure[0].x;
1849 sp->arm[i][LEFT][HAND].y = figure[1].y;
1850 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1851 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1852 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1853 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1854 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1860 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1862 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1863 int offset = (int)(s->angle*64*180/M_PI);
1864 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1865 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1867 /* Avoid wrapping */
1868 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1870 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1871 if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) {
1872 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1873 x - BALLRADIUS, y - BALLRADIUS,
1874 2*BALLRADIUS, 2*BALLRADIUS,
1876 } else if (s->divisions == 4) { /* 90 degree divisions */
1877 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1878 x - BALLRADIUS, y - BALLRADIUS,
1879 2*BALLRADIUS, 2*BALLRADIUS,
1880 offset % 23040, 5760);
1881 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1882 x - BALLRADIUS, y - BALLRADIUS,
1883 2*BALLRADIUS, 2*BALLRADIUS,
1884 (offset + 11520) % 23040, 5760);
1886 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1887 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1888 x - BALLRADIUS, y - BALLRADIUS,
1889 2*BALLRADIUS, 2*BALLRADIUS,
1890 (offset + 5760) % 23040, 5760);
1891 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1892 x - BALLRADIUS, y - BALLRADIUS,
1893 2*BALLRADIUS, 2*BALLRADIUS,
1894 (offset + 17280) % 23040, 5760);
1895 } else if (s->divisions == 2) { /* 180 degree divisions */
1896 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1897 x - BALLRADIUS, y - BALLRADIUS,
1898 2*BALLRADIUS, 2*BALLRADIUS,
1899 offset % 23040, 11520);
1901 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1902 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1903 x - BALLRADIUS, y - BALLRADIUS,
1904 2*BALLRADIUS, 2*BALLRADIUS,
1905 (offset + 11520) % 23040, 11520);
1907 (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1908 MI_SCREEN(mi), s->divisions);
1913 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1915 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1917 const double sa = sin(s->angle);
1918 const double ca = cos(s->angle);
1939 static const XPoint club[] = {
1952 {-24, 2}, /* 0 close boundary */
1954 XPoint a[XtNumber(club)];
1956 /* Avoid wrapping */
1957 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1959 /* Translate and fake perspective */
1960 for(i = 0; i < XtNumber(club); i++) {
1961 a[i].x = (short)(MI_WIDTH(mi)/2 +
1962 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1963 club[i].y*sqrt(sp->scale)*ca);
1964 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1965 club[i].y*sa*sqrt(sp->scale));
1968 if(color != MI_BLACK_PIXEL(mi)) {
1969 /* Outline in black */
1970 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1971 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1972 LineSolid, CapRound, JoinRound);
1973 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1974 a, XtNumber(a), CoordModeOrigin);
1977 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1979 /* Don't be tempted to optimize erase by drawing all the black in
1980 one X operation. It must use the same ops as the colours to
1981 guarantee a clean erase. */
1983 i = 0; /* Colored stripes */
1984 p[i++] = a[1]; p[i++] = a[2];
1985 p[i++] = a[9]; p[i++] = a[10];
1986 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1987 p, i, Convex, CoordModeOrigin);
1989 p[i++] = a[3]; p[i++] = a[4];
1990 p[i++] = a[7]; p[i++] = a[8];
1991 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1992 p, i, Convex, CoordModeOrigin);
1994 if(color != MI_BLACK_PIXEL(mi)) {
1995 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1998 i = 0; /* White center band */
1999 p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
2000 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2001 p, i, Convex, CoordModeOrigin);
2003 i = 0; /* White handle */
2004 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2005 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2006 p, i, Convex, CoordModeOrigin);
2008 i = 0; /* White tip */
2009 p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2010 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2011 p, i, Convex, CoordModeOrigin);
2016 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2018 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2020 const double sa = sin(s->angle);
2021 const double ca = cos(s->angle);
2041 static const XPoint club[] = {
2052 {-24, 2}, /* 0 close boundary */
2054 XPoint a[XtNumber(club)];
2056 /* Avoid wrapping */
2057 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2059 /* Translate and fake perspective */
2060 for(i = 0; i < XtNumber(club); i++) {
2061 a[i].x = (short)(MI_WIDTH(mi)/2 +
2062 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2063 club[i].y*sqrt(sp->scale)*ca);
2064 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2065 club[i].y*sa*sqrt(sp->scale));
2068 if(color != MI_BLACK_PIXEL(mi)) {
2069 /* Outline in black */
2070 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2071 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2072 LineSolid, CapRound, JoinRound);
2073 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2074 a, XtNumber(a), CoordModeOrigin);
2077 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2079 /* Don't be tempted to optimize erase by drawing all the black in
2080 one X operation. It must use the same ops as the colours to
2081 guarantee a clean erase. */
2083 i = 0; /* Coloured center band */
2084 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2085 p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2086 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2087 p, i, Convex, CoordModeOrigin);
2089 if(color != MI_BLACK_PIXEL(mi)) {
2090 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2093 i = 0; /* White handle */
2094 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2095 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2096 p, i, Convex, CoordModeOrigin);
2098 i = 0; /* White tip */
2099 p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2100 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2101 p, i, Convex, CoordModeOrigin);
2106 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2108 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2109 XPoint head, tail, last;
2110 DXPoint dhead, dlast;
2111 const double sa = sin(s->angle);
2112 const double ca = cos(s->angle);
2114 const double TailLen = -24;
2115 const double HeadLen = 16;
2116 const short Width = (short)(5 * sqrt(sp->scale));
2128 dhead.x = s->x + HeadLen * PERSPEC * sa;
2129 dhead.y = s->y - HeadLen * ca;
2131 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2133 } else { /* Store 'last' so we can use it later when s->prev has
2135 if(s->prev != s->next) {
2136 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2137 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2144 /* Avoid wrapping (after last is stored) */
2145 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2147 head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2148 head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2150 last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2151 last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2153 tail.x = (short)(MI_WIDTH(mi)/2 +
2154 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2155 tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2157 if(color != MI_BLACK_PIXEL(mi)) {
2158 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2159 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2160 Width, LineSolid, CapRound, JoinRound);
2161 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2162 head.x, head.y, tail.x, tail.y);
2164 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2165 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2166 Width * 2, LineSolid, CapRound, JoinRound);
2168 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2169 head.x, head.y, last.x, last.y);
2174 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2176 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2178 const double sa = sin(s->angle);
2179 const double ca = cos(s->angle);
2190 static const XPoint knife[] = {
2198 XPoint a[XtNumber(knife)], p[5];
2200 /* Avoid wrapping */
2201 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2203 /* Translate and fake perspective */
2204 for(i = 0; i < XtNumber(knife); i++) {
2205 a[i].x = (short)(MI_WIDTH(mi)/2 +
2206 (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2207 knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2208 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2209 knife[i].y*sa*sqrt(sp->scale));
2213 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2214 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2215 LineSolid, CapRound, JoinRound);
2216 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2217 a[0].x, a[0].y, a[4].x, a[4].y);
2220 if(color != MI_BLACK_PIXEL(mi)) {
2221 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2224 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2225 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2226 p, i, Convex, CoordModeOrigin);
2230 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2232 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2233 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2234 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2235 double radius = 15 * sp->scale;
2236 short thickness = (short)(8 * sqrt(sp->scale));
2238 /* Avoid wrapping */
2239 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2241 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2242 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2243 thickness, LineSolid, CapRound, JoinRound);
2245 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2246 (short)(x - radius*PERSPEC), (short)(y - radius),
2247 (short)(2*radius*PERSPEC), (short)(2*radius),
2253 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2255 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2256 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2257 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2258 double radius = 12 * sp->scale;
2259 int offset = (int)(s->angle*64*180/M_PI);
2260 int holesize = (int)(3.0*sqrt(sp->scale));
2262 /* Avoid wrapping */
2263 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2265 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2266 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2267 (short)(x - radius), (short)(y - radius),
2268 (short)(2*radius), (short)(2*radius),
2270 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2271 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2272 LineSolid, CapRound, JoinRound);
2273 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2274 (short)(x - radius), (short)(y - radius),
2275 (short)(2*radius), (short)(2*radius),
2278 /* Draw finger holes */
2279 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2280 LineSolid, CapRound, JoinRound);
2282 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2283 (short)(x - radius*0.5), (short)(y - radius*0.5),
2284 (short)(2*radius*0.5), (short)(2*radius*0.5),
2285 (offset + 960) % 23040, 0);
2286 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2287 (short)(x - radius*0.7), (short)(y - radius*0.7),
2288 (short)(2*radius*0.7), (short)(2*radius*0.7),
2289 (offset + 1920) % 23040, 0);
2290 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2291 (short)(x - radius*0.7), (short)(y - radius*0.7),
2292 (short)(2*radius*0.7), (short)(2*radius*0.7),
2296 /**************************************************************************
2297 * Public Functions *
2299 **************************************************************************/
2302 /* FIXME: refill_juggle currently just appends new throws to the
2303 * programme. This is fine if the programme is empty, but if there
2304 * are still some trajectories left then it really should take these
2308 refill_juggle(ModeInfo * mi)
2310 jugglestruct *sp = NULL;
2313 if (juggles == NULL)
2315 sp = &juggles[MI_SCREEN(mi)];
2317 /* generate pattern */
2318 if (pattern == NULL) {
2321 #define MAXREPEAT 300
2322 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2323 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2326 while (count < MI_CYCLES(mi)) {
2327 char buf[MAXPAT * 3 + 3], *b = buf;
2329 int l = NRAND(MAXPAT) + 1;
2330 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2332 { /* vary number of balls */
2333 int new_balls = sp->num_balls;
2336 if (new_balls == 2) /* Do not juggle 2 that often */
2337 change = NRAND(2 + CHANGE_BIAS / 4);
2339 change = NRAND(2 + CHANGE_BIAS);
2350 if (new_balls < sp->patternindex.minballs) {
2353 if (new_balls > sp->patternindex.maxballs) {
2356 if (new_balls < sp->num_balls) {
2357 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2360 sp->num_balls = new_balls;
2364 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2365 /* Pick from PortFolio */
2366 int p = sp->patternindex.index[sp->num_balls].start +
2367 NRAND(sp->patternindex.index[sp->num_balls].number);
2368 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2371 /* Invent a new pattern */
2373 for(i = 0; i < l; i++){
2375 do { /* Triangular Distribution => high values more likely */
2376 m = NRAND(sp->num_balls + 1);
2377 n = NRAND(sp->num_balls + 1);
2379 if (n == sp->num_balls) {
2382 switch(NRAND(5 + POSITION_BIAS)){
2383 case 0: /* Outside throw */
2385 case 1: /* Cross throw */
2387 case 2: /* Cross catch */
2389 case 3: /* Cross throw and catch */
2391 case 4: /* Bounce */
2394 break; /* Inside throw (default) */
2403 if (!program(mi, buf, NULL, t))
2408 } else { /* pattern supplied in height or 'a' notation */
2409 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2426 if (!projectile(sp)) {
2433 if(MI_IS_DEBUG(mi)) dump(sp);
2438 change_juggle(ModeInfo * mi)
2440 jugglestruct *sp = NULL;
2443 if (juggles == NULL)
2445 sp = &juggles[MI_SCREEN(mi)];
2447 /* Strip pending trajectories */
2448 for (t = sp->head->next; t != sp->head; t = t->next) {
2449 if(t->start > sp->time || t->finish < sp->time) {
2452 trajectory_destroy(n);
2456 /* Pick the current object theme */
2457 sp->objtypes = choose_object();
2461 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
2462 don't all those special effects. */
2463 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2465 show_figure(mi, MI_WHITE_PIXEL(mi), True);
2470 init_juggle (ModeInfo * mi)
2472 jugglestruct *sp = 0;
2475 MI_INIT (mi, juggles);
2476 sp = &juggles[MI_SCREEN(mi)];
2478 if (only && *only && strcmp(only, " ")) {
2479 balls = clubs = torches = knives = rings = bballs = False;
2480 if (!strcasecmp (only, "balls")) balls = True;
2481 else if (!strcasecmp (only, "clubs")) clubs = True;
2482 else if (!strcasecmp (only, "torches")) torches = True;
2483 else if (!strcasecmp (only, "knives")) knives = True;
2484 else if (!strcasecmp (only, "rings")) rings = True;
2485 else if (!strcasecmp (only, "bballs")) bballs = True;
2487 (void) fprintf (stderr,
2488 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2489 "\t rings, or bballs (not \"%s\")\n", only);
2490 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2496 if (sp->head == 0) { /* first time initializing this juggler */
2498 sp->count = ABS(MI_COUNT(mi));
2502 /* record start time */
2503 sp->begintime = time(NULL);
2504 if(sp->patternindex.maxballs > 0) {
2505 sp->num_balls = sp->patternindex.minballs +
2506 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2509 show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
2510 information about the juggler's
2513 /* "7" should be about three times the height of the juggler's
2515 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2516 7 * THROW_CATCH_INTERVAL);
2518 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2519 balls = True; /* Have to juggle something! */
2521 /* create circular trajectory list */
2522 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2523 if(sp->head == NULL){
2528 /* create circular object list */
2529 ADD_ELEMENT(Object, sp->objects, sp->objects);
2530 if(sp->objects == NULL){
2535 /* create circular wander list */
2536 ADD_ELEMENT(Wander, sp->wander, sp->wander);
2537 if(sp->wander == NULL){
2541 (void)wander(sp, 0); /* Initialize wander */
2543 sp->pattern = strdup(""); /* Initialise saved pattern with
2547 sp = &juggles[MI_SCREEN(mi)];
2551 !strcasecmp (pattern, ".") ||
2552 !strcasecmp (pattern, "random")))
2555 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2556 /* pattern list needs indexing */
2557 int nelements = XtNumber(portfolio);
2560 /* sort according to number of balls */
2561 qsort((void*)portfolio, nelements,
2562 sizeof(portfolio[1]), compare_num_balls);
2564 /* last pattern has most balls */
2565 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2566 /* run through sorted list, indexing start of each group
2567 and number in group */
2568 sp->patternindex.maxballs = 1;
2569 for (i = 0; i < nelements; i++) {
2570 int b = get_num_balls(portfolio[i].pattern);
2571 if (b > sp->patternindex.maxballs) {
2572 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2573 if(numpat == 0) sp->patternindex.minballs = b;
2574 sp->patternindex.maxballs = b;
2576 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2581 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2584 /* Set up programme */
2587 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
2588 only be resizing and then we won't all those special effects. */
2589 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2591 /* Only put things here that won't interrupt the programme during
2594 /* Use MIN so that users can resize in interesting ways, eg
2595 narrow windows for tall patterns, etc */
2596 sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2598 if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */
2599 char *font = get_string_resource (MI_DISPLAY(mi), "font", "Font");
2600 sp->mode_font = load_font_retry(MI_DISPLAY(mi), font);
2605 draw_juggle (ModeInfo * mi)
2607 Trajectory *traj = NULL;
2609 unsigned long future = 0;
2610 jugglestruct *sp = NULL;
2611 char *pattern = NULL;
2614 if (juggles == NULL)
2616 sp = &juggles[MI_SCREEN(mi)];
2618 MI_IS_DRAWN(mi) = True;
2621 /* Don't worry about flicker, trust Quartz's double-buffering.
2622 This is a fast fix for the pixel-turds I can't track down...
2624 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2630 (void)gettimeofday(&tv, NULL);
2631 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2633 sp->time += MI_DELAY(mi) / 1000;
2636 /* First pass: Move arms and strip out expired elements */
2637 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2638 if (traj->status != PREDICTOR) {
2639 /* Skip any elements that need further processing */
2640 /* We could remove them, but there shoudn't be many and they
2641 would be needed if we ever got the pattern refiller
2645 if (traj->start > future) { /* Lookahead to the end of the show */
2646 future = traj->start;
2648 if (sp->time < traj->start) { /* early */
2650 } else if (sp->time < traj->finish) { /* working */
2652 /* Look for pattern name */
2653 if(traj->pattern != NULL) {
2654 pattern=traj->pattern;
2657 if (traj->type == Empty || traj->type == Full) {
2658 /* Only interested in hands on this pass */
2659 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2660 double xd = 0, yd = 0;
2663 /* Find the catching offset */
2664 if(traj->object != NULL) {
2665 if(ObjectDefs[traj->object->type].handle > 0) {
2666 /* Handles Need to be oriented */
2667 xd = ObjectDefs[traj->object->type].handle *
2668 PERSPEC * sin(angle);
2669 yd = ObjectDefs[traj->object->type].handle *
2672 /* Balls are always caught at the bottom */
2677 p.x = (CUBIC(traj->xp, sp->time) - xd);
2678 p.y = (CUBIC(traj->yp, sp->time) + yd);
2679 reach_arm(mi, traj->hand, &p);
2681 /* Store updated hand position */
2685 if (traj->type == Ball || traj->type == Full) {
2686 /* Only interested in objects on this pass */
2690 if(traj->type == Full) {
2691 /* Adjusted these in the first pass */
2695 x = CUBIC(traj->xp, sp->time);
2696 y = CUBIC(traj->yp, sp->time);
2699 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2702 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2703 s->divisions = traj->divisions;
2704 traj->object->tracelen++;
2705 traj->object->active = True;
2707 } else { /* expired */
2708 Trajectory *n = traj;
2710 trajectory_destroy(n);
2714 /* Erase end of trails */
2715 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2717 for (s = o->trace->next;
2718 o->trace->next != o->trace &&
2719 (o->count == 0 || o->tracelen > o->tail);
2720 s = o->trace->next) {
2721 ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2724 if(o->count <= 0 && o->tracelen <= 0) {
2725 /* Object no longer in use and trail gone */
2730 if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2734 show_arms(mi, MI_BLACK_PIXEL(mi));
2735 cx = wander(sp, sp->time);
2736 /* Reduce flicker by only permitting movements of more than a pixel */
2737 if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2738 show_figure(mi, MI_BLACK_PIXEL(mi), False);
2742 show_figure(mi, MI_WHITE_PIXEL(mi), False);
2744 show_arms(mi, MI_WHITE_PIXEL(mi));
2747 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2749 ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2755 /* Save pattern name so we can erase it when it changes */
2756 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2757 /* Erase old name */
2758 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2760 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2761 0, 20, sp->pattern, strlen(sp->pattern));
2763 XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2764 0, 0, MI_WIDTH(mi), 25);
2767 sp->pattern = strdup(pattern);
2769 if (MI_IS_VERBOSE(mi)) {
2770 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2771 MI_SCREEN(mi), sp->pattern);
2774 if(sp->mode_font != None &&
2775 XTextWidth(sp->mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2776 /* Redraw once a cycle, in case it's obscured or it changed */
2777 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2778 XDrawImageString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2779 0, 20, sp->pattern, strlen(sp->pattern));
2783 if((int)(sp->time/10) % 1000 == 0)
2784 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2787 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2789 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2794 XSCREENSAVER_MODULE ("Juggle", juggle)
2796 #endif /* MODE_juggle */