1 /* -*- Mode: C; tab-width: 4 -*- */
4 #if !defined( lint ) && !defined( SABER )
5 static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore";
10 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
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.
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.
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
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
47 * Implement the anonymously promised -uni option.
52 * Notes on Adam Chalcraft Juggling Notation (used by permission)
53 * a-> Adam's notation s-> Site swap (Cambridge) notation
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.
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.
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]
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.
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
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)
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.
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
104 * ...55555555551551551551551...
106 * Now convert back to s-notation, to get
108 * ...55555566771771771771771...
110 * So the answer is to do two 6 throws and then go straight into
111 * (771). Coming back down of course,
113 * ...5515515515515515555555555...
117 * ...7717717717716615555555555...
119 * so the answer is to do a single 661 and then drop straight down to
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
128 /* This code uses so many linked lists it's worth having a built-in
134 # define DEFAULTS "*delay: 10000 \n" \
138 "*font: -*-times-bold-r-normal-*-180-*\n"
139 # define refresh_juggle 0
140 # define juggle_handle_event 0
141 # undef SMOOTH_COLORS
142 # include "xlockmore.h" /* in xscreensaver distribution */
143 #else /* STANDALONE */
144 # include "xlock.h" /* in xlockmore distribution */
145 #endif /* STANDALONE */
150 #define XClearWindow(d, w) \
152 XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \
153 XFillRectangle(d, w, MI_GC(mi), \
154 0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \
158 #define DEF_PATTERN "random" /* All patterns */
159 #define DEF_TAIL "1" /* No trace */
161 /* Maybe a ROLA BOLA would be at a better angle for viewing */
162 #define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
164 #define DEF_REAL "True"
165 #define DEF_DESCRIBE "True"
167 #define DEF_BALLS "True" /* Use Balls */
168 #define DEF_CLUBS "True" /* Use Clubs */
169 #define DEF_TORCHES "True" /* Use Torches */
170 #define DEF_KNIVES "True" /* Use Knives */
171 #define DEF_RINGS "True" /* Use Rings */
172 #define DEF_BBALLS "True" /* Use Bowling Balls */
175 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
178 static char *pattern;
184 static Bool describe;
193 static XrmOptionDescRec opts[] =
195 {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
196 {"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
198 {"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
199 {"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
201 {"-real", ".juggle.real", XrmoptionNoArg, "on" },
202 {"+real", ".juggle.real", XrmoptionNoArg, "off" },
203 {"-describe", ".juggle.describe", XrmoptionNoArg, "on" },
204 {"+describe", ".juggle.describe", XrmoptionNoArg, "off" },
205 {"-balls", ".juggle.balls", XrmoptionNoArg, "on" },
206 {"+balls", ".juggle.balls", XrmoptionNoArg, "off" },
207 {"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" },
208 {"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" },
209 {"-torches", ".juggle.torches", XrmoptionNoArg, "on" },
210 {"+torches", ".juggle.torches", XrmoptionNoArg, "off" },
211 {"-knives", ".juggle.knives", XrmoptionNoArg, "on" },
212 {"+knives", ".juggle.knives", XrmoptionNoArg, "off" },
213 {"-rings", ".juggle.rings", XrmoptionNoArg, "on" },
214 {"+rings", ".juggle.rings", XrmoptionNoArg, "off" },
215 {"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" },
216 {"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" },
217 {"-only", ".juggle.only", XrmoptionSepArg, NULL },
219 static argtype vars[] =
221 { &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
222 { &tail, "tail", "Tail", DEF_TAIL, t_Int },
224 { &uni, "uni", "Uni", DEF_UNI, t_Bool },
226 { &real, "real", "Real", DEF_REAL, t_Bool },
227 { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool },
228 { &balls, "balls", "Clubs", DEF_BALLS, t_Bool },
229 { &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool },
230 { &torches, "torches", "Torches", DEF_TORCHES, t_Bool },
231 { &knives, "knives", "Knives", DEF_KNIVES, t_Bool },
232 { &rings, "rings", "Rings", DEF_RINGS, t_Bool },
233 { &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool },
234 { &only, "only", "BBalls", " ", t_String },
236 static OptionStruct desc[] =
238 { "-pattern string", "Cambridge Juggling Pattern" },
239 { "-tail num", "Trace Juggling Patterns" },
241 { "-/+uni", "Unicycle" },
243 { "-/+real", "Real-time" },
244 { "-/+describe", "turn on/off pattern descriptions." },
245 { "-/+balls", "turn on/off Balls." },
246 { "-/+clubs", "turn on/off Clubs." },
247 { "-/+torches", "turn on/off Flaming Torches." },
248 { "-/+knives", "turn on/off Knives." },
249 { "-/+rings", "turn on/off Rings." },
250 { "-/+bballs", "turn on/off Bowling Balls." },
251 { "-only", "Turn off all objects but the named one." },
254 ENTRYPOINT ModeSpecOpt juggle_opts =
255 { XtNumber(opts), opts, XtNumber(vars), vars, desc };
258 ModStruct juggle_description = {
259 "juggle", "init_juggle", "draw_juggle", "release_juggle",
260 "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
261 10000, 200, 1000, 1, 64, 1.0, "",
262 "Shows a Juggler, juggling", 0, NULL
268 # include <X11/unix_time.h>
271 /* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All
272 "thicknesses" are scaled by sqrt(sp->scale) so that they are
273 proportionally thicker for smaller windows. Objects spinning out
274 of the plane (such as clubs) fake perspective by compressing their
275 horizontal coordinates by PERSPEC */
279 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
281 #define BALLRADIUS ARMWIDTH
286 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
288 /* Timing based on count. Units are milliseconds. Juggles per second
289 is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
291 #define THROW_CATCH_INTERVAL (sp->count)
292 #define THROW_NULL_INTERVAL (sp->count * 0.5)
293 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
295 /********************************************************************
296 * Trace Definitions *
298 * These record rendering data so that a drawn object can be erased *
299 * later. Each object has its own Trace list. *
301 ********************************************************************/
303 typedef struct {double x, y; } DXPoint;
304 typedef struct trace *TracePtr;
305 typedef struct trace {
316 /*******************************************************************
317 * Object Definitions *
319 * These describe the various types of Object that can be juggled *
321 *******************************************************************/
322 typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
324 static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
325 static DrawProc show_ring, show_bball;
327 typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
328 NUM_OBJECT_TYPES} ObjType;
329 #define OBJMIXPROB 20 /* inverse of the chances of using an odd
330 object in the pattern */
332 static const struct {
333 DrawProc *draw; /* Object Rendering function */
334 int handle; /* Length of object's handle */
335 int mintrail; /* Minimum trail length */
336 double cor; /* Coefficient of Restitution. perfect bounce = 1 */
337 double weight; /* Heavier objects don't get thrown as high */
350 0.55, /* Clubs don't bounce too well */
356 20, /* Torches need flames */
357 0, /* Torches don't bounce -- fire risk! */
364 0, /* Knives don't bounce */
383 /**************************
384 * Trajectory definitions *
385 **************************/
387 typedef enum {HEIGHT, ADAM} Notation;
388 typedef enum {Empty, Full, Ball} Throwable;
389 typedef enum {LEFT, RIGHT} Hand;
390 typedef enum {THROW, CATCH} Action;
391 typedef enum {HAND, ELBOW, SHOULDER} Joint;
392 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
393 PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
394 typedef struct {double a, b, c, d; } Spline;
395 typedef DXPoint Arm[3];
397 /* A Wander contains a Spline and a time interval. A list of Wanders
398 * describes the performer's position as he moves around the screen. */
399 typedef struct wander *WanderPtr;
400 typedef struct wander {
401 WanderPtr next, prev;
403 unsigned long finish;
410 /* Object is an arbitrary object being juggled. Each Trajectory
411 * references an Object ("count" tracks this), and each Object is also
412 * linked into a global Objects list. Objects may include a Trace
413 * list for tracking erasures. */
414 typedef struct object *ObjectPtr;
415 typedef struct object {
416 ObjectPtr next, prev;
420 int count; /* reference count */
421 Bool active; /* Object is in use */
431 /* Trajectory is a segment of juggling action. A list of Trajectories
432 * defines the juggling performance. The Trajectory list goes through
433 * multiple processing steps to convert it from basic juggling
434 * notation into rendering data. */
436 typedef struct trajectory *TrajectoryPtr;
437 typedef struct trajectory {
438 TrajectoryPtr prev, next; /* for building list */
439 TrajectoryStatus status;
457 TrajectoryPtr balllink;
458 TrajectoryPtr handlink;
461 double cx; /* Moving juggler */
462 double x, y; /* current position */
463 double dx, dy; /* initial velocity */
467 unsigned long start, finish;
481 const char * pattern;
485 /* List of popular patterns, in any order */
486 /* Patterns should be given in Adam notation so the generator can
487 concatenate them safely. Null descriptions are ok. Height
488 notation will be displayed automatically. */
489 /* Can't const this because it is qsorted. This *should* be reentrant,
491 static /*const*/ patternstruct portfolio[] = {
492 {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
493 {"[2 0]", /* 4 0 */ "2 in 1 hand"},
494 {"[2 0 1]", /* 5 0 1 */},
495 {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
496 {"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
497 {"[2 0 1 1]", /* 6 0 1 1 */},
499 {"[3]", /* 3 */ "3 cascade"},
500 {"[+3]", /* +3 */ "reverse 3 cascade"},
501 {"[=3]", /* =3 */ "cascade 3 under arm"},
502 {"[&3]", /* &3 */ "cascade 3 catching under arm"},
503 {"[_3]", /* _3 */ "bouncing 3 cascade"},
504 {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
505 {"[3 2 1]", /* 5 3 1" */},
506 {"[3 3 1]", /* 4 4 1" */},
507 {"[3 1 2]", /* 6 1 2 */ "See-saw"},
508 {"[=3 3 1 2]", /* =4 5 1 2 */},
509 {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
510 {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
511 {"[3 3 1]", /* 4 4 1 */},
512 {"[+3 2 3]", /* +4 2 3 */},
513 {"[+3 1]", /* +5 1 */ "3 shower"},
514 {"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
515 {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
516 {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
517 {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
518 {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
519 {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
520 {"[3 2 2 0 3 2 0 2 3 0 2 2 0]", /* 7 3 3 0 7 3 0 3 7 0 3 3 0 */},
521 {"[3 0 2 0]", /* 8 0 4 0 */},
522 {"[_3 2 1]", /* _5 3 1 */},
523 {"[_3 0 1]", /* _8 0 1 */},
524 {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
525 {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
527 {"[4]", /* 4 */ "4 cascade"},
528 {"[+4 3]", /* +5 3 */ "4 ball half shower"},
529 {"[4 4 2]", /* 5 5 2 */},
530 {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
531 {"[+4 3 +4]", /* +5 3 +4 */},
532 {"[4 3 4 4]", /* 5 3 4 4 */},
533 {"[4 3 3 4]", /* 6 3 3 4 */},
534 {"[4 3 2 4", /* 6 4 2 4 */},
535 {"[+4 1]", /* +7 1 */ "4 shower"},
536 {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
537 {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
538 {"[+4 2 1 3]", /* +9 3 1 3 */},
539 {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
540 {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
541 {"[_4 3 3]", /* _6 3 3 */},
542 {"[_4 3 1]", /* _7 4 1 */},
543 {"[_4 2 1]", /* _8 3 1 */},
544 {"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
545 {"[_4 1 3 1]", /* _9 1 5 1 */},
546 {"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
548 {"[5]", /* 5 */ "5 cascade"},
549 {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
550 {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
551 {"[5 4 4]", /* 7 4 4 */},
552 {"[_5 4 4]", /* _7 4 4 */},
553 {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
554 {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
555 {"[_5 4 1 +4]", /* _9 5 1 5 */},
556 {"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
557 {"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
559 {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
561 {"[6]", /* 6 */ "6 cascade"},
562 {"[+6 5]", /* +7 5 */},
563 {"[6 4]", /* 8 4 */},
564 {"[+6 3]", /* +9 3 */},
565 {"[6 5 4 4]", /* 9 7 4 4 */},
566 {"[+6 5 5 5]", /* +9 5 5 5 */},
567 {"[6 0 6]", /* 9 0 9 */},
568 {"[_6 0 _6]", /* _9 0 _9 */},
570 {"[_7]", /* _7 */ "bouncing 7 cascade"},
571 {"[7]", /* 7 */ "7 cascade"},
572 {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
578 typedef struct { int start; int number; } PatternIndex;
580 struct patternindex {
583 PatternIndex index[XtNumber(portfolio)];
587 /* Jugglestruct: per-screen global data. The master Wander, Object
588 * and Trajectory lists are anchored here. */
599 time_t begintime; /* should make 'time' usable for at least 48 days
600 on a 32-bit machine */
601 unsigned long time; /* millisecond timer*/
604 struct patternindex patternindex;
605 XFontStruct *mode_font;
608 static jugglestruct *juggles = (jugglestruct *) NULL;
614 #define DUP_OBJECT(n, t) { \
615 (n)->object = (t)->object; \
616 if((n)->object != NULL) (n)->object->count++; \
619 /* t must point to an existing element. t must not be an
620 expression ending ->next or ->prev */
621 #define REMOVE(t) { \
622 (t)->next->prev = (t)->prev; \
623 (t)->prev->next = (t)->next; \
627 /* t receives element to be created and added to the list. ot must
628 point to an existing element or be identical to t to start a new
629 list. Applicable to Trajectories, Objects and Traces. */
630 #define ADD_ELEMENT(type, t, ot) \
631 if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
632 (t)->next = (ot)->next; \
635 (t)->next->prev = (t); \
639 object_destroy(Object* o)
641 if(o->trace != NULL) {
642 while(o->trace->next != o->trace) {
643 Trace *s = o->trace->next;
644 REMOVE(s); /* Don't eliminate 's' */
652 trajectory_destroy(Trajectory *t) {
653 if(t->name != NULL) free(t->name);
654 if(t->pattern != NULL) free(t->pattern);
655 /* Reduce object link count and call destructor if necessary */
656 if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
657 object_destroy(t->object);
659 REMOVE(t); /* Unlink and free */
663 free_juggle(jugglestruct *sp) {
664 if (sp->head != NULL) {
665 while (sp->head->next != sp->head) {
666 trajectory_destroy(sp->head->next);
669 sp->head = (Trajectory *) NULL;
671 if(sp->objects != NULL) {
672 while (sp->objects->next != sp->objects) {
673 object_destroy(sp->objects->next);
676 sp->objects = (Object*)NULL;
678 if(sp->wander != NULL) {
679 while (sp->wander->next != sp->wander) {
680 Wander *w = sp->wander->next;
684 sp->wander = (Wander*)NULL;
686 if(sp->pattern != NULL) {
690 if (sp->mode_font!=None) {
691 XFreeFontInfo(NULL,sp->mode_font,1);
692 sp->mode_font = None;
697 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
701 ADD_ELEMENT(Trajectory, t, sp->head->prev);
702 if(t == NULL){ /* Out of Memory */
708 t->name = strdup(name);
721 /* add a Thratch to the performance */
723 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
725 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
731 if (MI_IS_VERBOSE(mi)) {
732 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
733 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
736 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
737 have been programmed */
738 /* title is the pattern name to be supplied to the first throw of
739 a sequence. If no name if given, use an empty title so that
740 the sequences are still delimited. */
741 const char *title = (name != NULL)? name : "";
746 for(p=patn; *p; p++) {
747 if (*p >= '0' && *p <='9') {
749 h = 10*h + (*p - '0');
751 Notation nn = notation;
753 case '[': /* begin Adam notation */
756 case '-': /* Inside throw */
759 case '+': /* Outside throw */
760 case '=': /* Cross throw */
761 case '&': /* Cross catch */
762 case 'x': /* Cross throw and catch */
763 case '_': /* Bounce */
764 case 'k': /* Kickup */
767 case '*': /* Lose ball */
771 case ']': /* end Adam notation */
777 if (!add_throw(sp, type, h, notation, title))
787 if(w == 0) { /* Only warn on first pass */
788 (void) fprintf(stderr,
789 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
796 if (seen) { /* end of sequence */
797 if (!add_throw(sp, type, h, notation, title))
811 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
813 4 4 1 3 12 2 4 1 4 4 13 0 3 1
816 #define BOUNCEOVER 10
820 /* Convert Adam notation into heights */
822 adam(jugglestruct *sp)
825 for(t = sp->head->next; t != sp->head; t = t->next) {
826 if (t->status == ATCH) {
829 for(p = t->next; a > 0; p = p->next) {
831 t->height = -9; /* Indicate end of processing for name() */
834 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
839 if(t->height > BOUNCEOVER && t->posn == ' '){
840 t->posn = '_'; /* high defaults can be bounced */
841 } else if(t->height < 3 && t->posn == '_') {
842 t->posn = ' '; /* Can't bounce short throws. */
844 if(t->height < KICKMIN && t->posn == 'k'){
845 t->posn = ' '; /* Can't kick short throws */
847 if(t->height > THROWMAX){
848 t->posn = 'k'; /* Use kicks for ridiculously high throws */
855 /* Discover converted heights and update the sequence title */
857 name(jugglestruct *sp)
862 for(t = sp->head->next; t != sp->head; t = t->next) {
863 if (t->status == THRATCH && t->name != NULL) {
865 for(p = t; p == t || p->name == NULL; p = p->next) {
866 if(p == sp->head || p->height < 0) { /* end of reliable data */
870 b += sprintf(b, " %d", p->height);
872 b += sprintf(b, " %c%d", p->posn, p->height);
874 if(b - buffer > 500) break; /* otherwise this could eventually
875 overflow. It'll be too big to
879 (void) sprintf(b, ", %s", t->name);
881 free(t->name); /* Don't need name any more, it's been converted
884 if(t->pattern != NULL) free(t->pattern);
885 t->pattern = strdup(buffer);
890 /* Split Thratch notation into explicit throws and catches.
891 Usually Catch follows Throw in same hand, but take care of special
894 /* ..n1.. -> .. LTn RT1 LC RC .. */
895 /* ..nm.. -> .. LTn LC RTm RC .. */
898 part(jugglestruct *sp)
900 Trajectory *t, *nt, *p;
901 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
903 for (t = sp->head->next; t != sp->head; t = t->next) {
904 if (t->status > THRATCH) {
906 } else if (t->status == THRATCH) {
909 /* plausibility check */
910 if (t->height <= 2 && t->posn == '_') {
911 t->posn = ' '; /* no short bounces */
913 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
914 t->posn = ' '; /* 1's need close catches */
919 case ' ': posn = '-'; t->posn = '+'; break;
920 case '+': posn = '+'; t->posn = '-'; break;
921 case '=': posn = '='; t->posn = '+'; break;
922 case '&': posn = '+'; t->posn = '='; break;
923 case 'x': posn = '='; t->posn = '='; break;
924 case '_': posn = '_'; t->posn = '-'; break;
925 case 'k': posn = 'k'; t->posn = 'k'; break;
927 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
930 hand = (Hand) ((hand + 1) % 2);
935 if (t->height == 1 && p != sp->head) {
936 p = p->prev; /* '1's are thrown earlier than usual */
942 ADD_ELEMENT(Trajectory, nt, p);
950 nt->height = t->height;
960 choose_object(void) {
963 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
964 if(balls && o == BALL) break;
965 if(clubs && o == CLUB) break;
966 if(torches && o == TORCH) break;
967 if(knives && o == KNIFE) break;
968 if(rings && o == RING) break;
969 if(bballs && o == BBALLS) break;
974 /* Connnect up throws and catches to figure out which ball goes where.
975 Do the same with the juggler's hands. */
980 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
983 for (t = sp->head->next; t != sp->head; t = t->next) {
984 if (t->status == ACTION) {
985 if (t->action == THROW) {
986 if (t->type == Empty) {
987 /* Create new Object */
988 ADD_ELEMENT(Object, t->object, sp->objects);
989 t->object->count = 1;
990 t->object->tracelen = 0;
991 t->object->active = False;
992 /* Initialise object's circular trace list */
993 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
995 if (MI_NPIXELS(mi) > 2) {
996 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
999 t->object->color = 1;
1001 t->object->color = 0;
1005 /* Small chance of picking a random object instead of the
1007 if(NRAND(OBJMIXPROB) == 0) {
1008 t->object->type = choose_object();
1010 t->object->type = sp->objtypes;
1013 /* Check to see if we need trails for this object */
1014 if(tail < ObjectDefs[t->object->type].mintrail) {
1015 t->object->tail = ObjectDefs[t->object->type].mintrail;
1017 t->object->tail = tail;
1021 /* Balls can change divisions at each throw */
1022 t->divisions = 2 * (NRAND(2) + 1);
1024 /* search forward for next catch in this hand */
1025 for (p = t->next; t->handlink == NULL; p = p->next) {
1026 if(p->status < ACTION || p == sp->head) return;
1027 if (p->action == CATCH) {
1028 if (t->handlink == NULL && p->hand == t->hand) {
1034 if (t->height > 0) {
1037 /* search forward for next ball catch */
1038 for (p = t->next; t->balllink == NULL; p = p->next) {
1039 if(p->status < ACTION || p == sp->head) {
1043 if (p->action == CATCH) {
1044 if (t->balllink == NULL && --h < 1) { /* caught */
1045 t->balllink = p; /* complete trajectory */
1047 if (p->type == Full) {
1048 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1049 MI_SCREEN(mi), t->object->color);
1053 DUP_OBJECT(p, t); /* accept catch */
1054 p->angle = t->angle;
1055 p->divisions = t->divisions;
1060 t->type = Empty; /* thrown */
1061 } else if (t->action == CATCH) {
1062 /* search forward for next throw from this hand */
1063 for (p = t->next; t->handlink == NULL; p = p->next) {
1064 if(p->status < ACTION || p == sp->head) return;
1065 if (p->action == THROW && p->hand == t->hand) {
1066 p->type = t->type; /* pass ball */
1067 DUP_OBJECT(p, t); /* pass object */
1068 p->divisions = t->divisions;
1073 t->status = LINKEDACTION;
1078 /* Clap when both hands are empty */
1080 clap(jugglestruct *sp)
1083 for (t = sp->head->next; t != sp->head; t = t->next) {
1084 if (t->status == LINKEDACTION &&
1085 t->action == CATCH &&
1087 t->handlink != NULL &&
1088 t->handlink->height == 0) { /* Completely idle hand */
1090 for (p = t->next; p != sp->head; p = p->next) {
1091 if (p->status == LINKEDACTION &&
1092 p->action == CATCH &&
1093 p->hand != t->hand) { /* Next catch other hand */
1094 if(p->type == Empty &&
1095 p->handlink != NULL &&
1096 p->handlink->height == 0) { /* Also completely idle */
1098 t->handlink->posn = '^'; /* Move first hand's empty throw */
1099 p->posn = '^'; /* to meet second hand's empty
1103 break; /* Only need first catch */
1110 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1112 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1113 with velocity dx1 at time t1 */
1115 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1124 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1125 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1130 s.c = (3*a*t0 - 2*b)*t0 + c;
1131 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1135 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1136 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1137 velocity dx2 at time t2. The arrival and departure velocities at
1138 x1, t1 must be the same. */
1140 makeSplinePair(Spline *s1, Spline *s2,
1141 double x0, double dx0, int t0,
1143 double x2, double dx2, int t2)
1145 double x10, x21, t21, t10, t20, dx1;
1151 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1152 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1154 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1155 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1159 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1160 from x at time t at constant velocity dx. sy goes from y at time t
1161 with velocity dy and constant acceleration g. */
1163 makeParabola(Trajectory *n,
1164 double x, double dx, double y, double dy, double g)
1166 double t = (double)n->start;
1170 n->xp.d = -dx*t + x;
1173 n->yp.c = -g*t + dy;
1174 n->yp.d = g/2*t*t - dy*t + y;
1179 /* Make juggler wander around the screen */
1180 static double wander(jugglestruct *sp, unsigned long time)
1183 for (w = sp->wander->next; w != sp->wander; w = w->next) {
1184 if (w->finish < sp->time) { /* expired */
1188 } else if(w->finish > time) {
1192 if(w == sp->wander) { /* Need a new one */
1193 ADD_ELEMENT(Wander, w, sp->wander->prev);
1194 if(w == NULL) { /* Memory problem */
1197 w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1201 w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1203 w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1205 return CUBIC(w->s, time);
1208 #define SX 25 /* Shoulder Width */
1210 /* Convert hand position symbols into actual time/space coordinates */
1212 positions(jugglestruct *sp)
1215 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1216 for (t = sp->head->next; t != sp->head; t = t->next) {
1217 if (t->status >= PTHRATCH) {
1219 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1220 /* Allow ACTIONs to be annotated, but we won't mark them ready
1221 for the next stage */
1228 if (t->action == CATCH) { /* Throw-to-catch */
1229 if (t->type == Empty) {
1230 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1231 } else { /* successful catch */
1232 now += (int)(THROW_CATCH_INTERVAL);
1234 } else { /* Catch-to-throw */
1235 if(t->object != NULL) {
1236 now += (int) (CATCH_THROW_INTERVAL *
1237 ObjectDefs[t->object->type].weight);
1239 now += (int) (CATCH_THROW_INTERVAL);
1245 else /* Concatenated performances may need clock resync */
1248 t->cx = wander(sp, t->start);
1253 /* Add room for the handle */
1254 if(t->action == CATCH && t->object != NULL)
1255 yo -= ObjectDefs[t->object->type].handle;
1258 case '-': xo = sx - pose; break;
1261 case '+': xo = sx + pose; break;
1263 case '=': xo = - sx - pose; yo += pose; break;
1264 case '^': xo = 0; yo += pose*2; break; /* clap */
1266 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1270 t->angle = (((t->hand == LEFT) ^
1271 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1274 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1277 /* Only mark complete if it was already linked */
1278 if(t->status == LINKEDACTION) {
1279 t->status = PTHRATCH;
1286 /* Private physics functions */
1288 /* Compute the spin-rate for a trajectory. Different types of throw
1289 (eg, regular thows, bounces, kicks, etc) have different spin
1292 type = type of object
1293 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1294 old = earlier spin to consider
1295 dt = time span of this trajectory
1296 height = height of ball throw or 0 if based on old spin
1297 turns = full club turns required during this operation
1298 togo = partial club turns required to match hands
1301 spinrate(ObjType type, Trajectory *h, double old, double dt,
1302 int height, int turns, double togo)
1304 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1306 if(ObjectDefs[type].handle != 0) { /* Clubs */
1307 return (dir * turns * 2 * M_PI + togo) / dt;
1308 } else if(height == 0) { /* Balls already spinning */
1310 } else { /* Balls */
1311 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1316 /* compute the angle at the end of a spinning trajectory */
1318 end_spin(Trajectory *t)
1320 return t->angle + t->spin * (t->finish - t->start);
1323 /* Sets the initial angle of the catch following hand movement t to
1324 the final angle of the throw n. Also sets the angle of the
1325 subsequent throw to the same angle plus half a turn. */
1327 match_spins_on_catch(Trajectory *t, Trajectory *n)
1329 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1330 t->balllink->angle = end_spin(n);
1331 if(t->balllink->handlink != NULL) {
1332 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1338 find_bounce(jugglestruct *sp,
1339 double yo, double yf, double yc, double tc, double cor)
1341 double tb, i, dy = 0;
1342 const double e = 1; /* permissible error in yc */
1346 yt = height at catch time after one bounce
1347 one or three roots according to timing
1348 find one by interval bisection
1351 for(i = tc / 2; i > 0.0001; i/=2){
1354 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1357 dy = (yf - yo)/tb + sp->Gr/2*tb;
1359 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1362 }else if(yt > yc - e){
1368 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1375 new_predictor(const Trajectory *t, int start, int finish, double angle)
1378 ADD_ELEMENT(Trajectory, n, t->prev);
1383 n->divisions = t->divisions;
1385 n->status = PREDICTOR;
1393 /* Turn abstract timings into physically appropriate object trajectories. */
1395 projectile(jugglestruct *sp)
1398 const int yf = 0; /* Floor height */
1400 for (t = sp->head->next; t != sp->head; t = t->next) {
1401 if (t->status != PTHRATCH || t->action != THROW) {
1403 } else if (t->balllink == NULL) { /* Zero Throw */
1404 t->status = BPREDICTOR;
1405 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1407 } else if(t->balllink == t->handlink) {
1408 /* '2' height - hold on to ball. Don't need to consider
1409 flourishes, 'hands' will do that automatically anyway */
1412 /* Zero spin to avoid wrist injuries */
1414 match_spins_on_catch(t, t);
1416 t->status = BPREDICTOR;
1419 if (t->posn == '_') { /* Bounce once */
1421 const int tb = t->start +
1422 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1423 (double) (t->balllink->start - t->start),
1424 ObjectDefs[t->object->type].cor);
1426 if(tb < t->start) { /* bounce too hard */
1427 t->posn = '+'; /* Use regular throw */
1429 Trajectory *n; /* First (throw) trajectory. */
1430 double dt; /* Time span of a trajectory */
1431 double dy; /* Distance span of a follow-on trajectory.
1432 First trajectory uses t->dy */
1433 /* dx is constant across both trajectories */
1434 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1436 { /* ball follows parabola down */
1437 n = new_predictor(t, t->start, tb, t->angle);
1438 if(n == NULL) return False;
1439 dt = n->finish - n->start;
1440 /* Ball rate 4, no flight or matching club turns */
1441 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1442 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1443 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1446 { /* ball follows parabola up */
1447 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1449 if(m == NULL) return False;
1450 dt = m->finish - m->start;
1451 /* Use previous ball rate, no flight club turns */
1452 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1453 t->balllink->angle - m->angle);
1454 match_spins_on_catch(t, m);
1455 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1456 makeParabola(m, t->balllink->x - t->dx * dt,
1457 t->dx, (double) yf, dy, sp->Gr);
1460 t->status = BPREDICTOR;
1463 } else if (t->posn == 'k') { /* Drop & Kick */
1464 Trajectory *n; /* First (drop) trajectory. */
1465 Trajectory *o; /* Second (rest) trajectory */
1466 Trajectory *m; /* Third (kick) trajectory */
1467 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1468 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1471 { /* Fall to ground */
1472 n = new_predictor(t, t->start, td, t->angle);
1473 if(n == NULL) return False;
1474 dt = n->finish - n->start;
1475 /* Ball spin rate 4, no flight club turns */
1476 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1477 t->balllink->angle - n->angle);
1478 t->dx = (t->balllink->x - t->x) / dt;
1479 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1480 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1483 { /* Rest on ground */
1484 o = new_predictor(t, n->finish, tk, end_spin(n));
1485 if(o == NULL) return False;
1487 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1492 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1493 if(m == NULL) return False;
1494 dt = m->finish - m->start;
1495 /* Match receiving hand, ball rate 4, one flight club turn */
1496 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1497 4, 1, t->balllink->angle - m->angle);
1498 match_spins_on_catch(t, m);
1499 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1500 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1503 t->status = BPREDICTOR;
1507 /* Regular flight, no bounce */
1508 { /* ball follows parabola */
1510 Trajectory *n = new_predictor(t, t->start,
1511 t->balllink->start, t->angle);
1512 if(n == NULL) return False;
1513 dt = t->balllink->start - t->start;
1515 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1516 t->balllink->angle - n->angle);
1517 match_spins_on_catch(t, n);
1518 t->dx = (t->balllink->x - t->x) / dt;
1519 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1520 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1523 t->status = BPREDICTOR;
1529 /* Turn abstract hand motions into cubic splines. */
1531 hands(jugglestruct *sp)
1533 Trajectory *t, *u, *v;
1535 for (t = sp->head->next; t != sp->head; t = t->next) {
1536 /* no throw => no velocity */
1537 if (t->status != BPREDICTOR) {
1542 if (u == NULL) { /* no next catch */
1546 if (v == NULL) { /* no next throw */
1550 /* double spline takes hand from throw, thru catch, to
1553 t->finish = u->start;
1554 t->status = PREDICTOR;
1556 u->finish = v->start;
1557 u->status = PREDICTOR;
1560 /* FIXME: These adjustments leave a small glitch when alternating
1561 balls and clubs. Just hope no-one notices. :-) */
1563 /* make sure empty hand spin matches the thrown object in case it
1566 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1567 fabs((u->angle - t->angle)/(u->start - t->start));
1569 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1570 fabs((v->angle - u->angle)/(v->start - u->start));
1572 (void) makeSplinePair(&t->xp, &u->xp,
1573 t->x, t->dx, t->start,
1575 v->x, v->dx, v->start);
1576 (void) makeSplinePair(&t->yp, &u->yp,
1577 t->y, t->dy, t->start,
1579 v->y, v->dy, v->start);
1581 t->status = PREDICTOR;
1585 /* Given target x, y find_elbow puts hand at target if possible,
1586 * otherwise makes hand point to the target */
1588 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1592 double x = p->x - s->x;
1593 double y = p->y - s->y;
1594 h2 = x*x + y*y + z*z;
1595 if (h2 > 4 * armlength * armlength) {
1596 t = armlength/sqrt(h2);
1599 h->x = 2 * t * x + s->x;
1600 h->y = 2 * t * y + s->y;
1602 r = sqrt((double)(x*x + z*z));
1603 t = sqrt(4 * armlength * armlength / h2 - 1);
1604 e->x = x*(1 + y*t/r)/2 + s->x;
1605 e->y = (y - r*t)/2 + s->y;
1612 /* NOTE: returned x, y adjusted for arm reach */
1614 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1616 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1618 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1619 *p = sp->arm[1][side][HAND] = h;
1620 sp->arm[1][side][ELBOW] = e;
1624 /* dumps a human-readable rendition of the current state of the juggle
1625 pipeline to stderr for debugging */
1627 dump(jugglestruct *sp)
1630 for (t = sp->head->next; t != sp->head; t = t->next) {
1631 switch (t->status) {
1633 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1636 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1637 t->pattern == NULL?"":t->pattern);
1640 if (t->action == CATCH)
1641 (void) fprintf(stderr, "%p A %c%cC\n",
1643 t->hand ? 'R' : 'L');
1645 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1647 t->hand ? 'R' : 'L',
1648 (t->action == THROW)?'T':'N',
1652 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1655 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1656 t->height, t->object == NULL?0:t->object->color,
1657 (void*)t->handlink, (void*)t->balllink);
1660 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1663 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1664 t->height, t->type, t->object == NULL?0:t->object->color,
1665 t->start, t->finish);
1668 (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1669 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1670 t->object == NULL?0:t->object->color,
1671 t->start, t->finish, t->yp.c);
1674 (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1675 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1676 t->object == NULL?0:t->object->color,
1677 t->start, t->finish, t->yp.c);
1680 (void) fprintf(stderr, "%p: status %d not implemented\n",
1681 (void*)t, t->status);
1685 (void) fprintf(stderr, "---\n");
1689 static int get_num_balls(const char *j)
1695 for (p = j; *p; p++) {
1696 if (*p >= '0' && *p <='9') { /* digit */
1697 h = 10*h + (*p - '0');
1713 compare_num_balls(const void *p1, const void *p2)
1716 i = get_num_balls(((patternstruct*)p1)->pattern);
1717 j = get_num_balls(((patternstruct*)p2)->pattern);
1732 /**************************************************************************
1733 * Rendering Functions *
1735 **************************************************************************/
1738 show_arms(ModeInfo * mi, unsigned long color)
1740 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1743 XPoint a[XtNumber(sp->arm[0][0])];
1744 if(color == MI_BLACK_PIXEL(mi)) {
1749 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1750 ARMWIDTH, LineSolid, CapRound, JoinRound);
1751 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1752 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1753 /* Translate into device coords */
1754 for(i = 0; i < XtNumber(a); i++) {
1755 a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1756 a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
1758 sp->arm[0][side][i] = sp->arm[1][side][i];
1760 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1761 a, XtNumber(a), CoordModeOrigin);
1766 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1768 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1789 static const XPoint figure[] = {
1790 { 15, 70}, /* 0 Left Hip */
1791 { 0, 90}, /* 1 Waist */
1792 { SX, 130}, /* 2 Left Shoulder */
1793 {-SX, 130}, /* 3 Right Shoulder */
1794 {-15, 70}, /* 4 Right Hip */
1795 { 0, 130}, /* 5 Neck */
1796 { 0, 140}, /* 6 Chin */
1797 { SX, 0}, /* 7 Left Foot */
1798 {-SX, 0}, /* 8 Right Foot */
1799 {-17, 174}, /* 9 Head1 */
1800 { 17, 140}, /* 10 Head2 */
1802 XPoint a[XtNumber(figure)];
1804 /* Translate into device coords */
1805 for(i = 0; i < XtNumber(figure); i++) {
1806 a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1807 a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1810 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1811 ARMWIDTH, LineSolid, CapRound, JoinRound);
1812 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1815 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1816 p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1817 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1818 p, i, CoordModeOrigin);
1821 p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1822 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1823 p, i, CoordModeOrigin);
1826 p[i++] = a[5]; p[i++] = a[6];
1827 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1828 p, i, CoordModeOrigin);
1831 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1833 a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1834 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1835 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1837 /* Initialise arms */
1839 for(i = 0; i < 2; i++){
1840 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1841 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1842 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1843 sp->arm[i][LEFT][HAND].x = figure[0].x;
1844 sp->arm[i][LEFT][HAND].y = figure[1].y;
1845 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1846 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1847 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1848 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1849 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1855 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1857 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1858 int offset = (int)(s->angle*64*180/M_PI);
1859 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1860 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1862 /* Avoid wrapping */
1863 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1865 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1866 if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) {
1867 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1868 x - BALLRADIUS, y - BALLRADIUS,
1869 2*BALLRADIUS, 2*BALLRADIUS,
1871 } else if (s->divisions == 4) { /* 90 degree divisions */
1872 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1873 x - BALLRADIUS, y - BALLRADIUS,
1874 2*BALLRADIUS, 2*BALLRADIUS,
1875 offset % 23040, 5760);
1876 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1877 x - BALLRADIUS, y - BALLRADIUS,
1878 2*BALLRADIUS, 2*BALLRADIUS,
1879 (offset + 11520) % 23040, 5760);
1881 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1882 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1883 x - BALLRADIUS, y - BALLRADIUS,
1884 2*BALLRADIUS, 2*BALLRADIUS,
1885 (offset + 5760) % 23040, 5760);
1886 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1887 x - BALLRADIUS, y - BALLRADIUS,
1888 2*BALLRADIUS, 2*BALLRADIUS,
1889 (offset + 17280) % 23040, 5760);
1890 } else if (s->divisions == 2) { /* 180 degree divisions */
1891 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1892 x - BALLRADIUS, y - BALLRADIUS,
1893 2*BALLRADIUS, 2*BALLRADIUS,
1894 offset % 23040, 11520);
1896 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1897 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1898 x - BALLRADIUS, y - BALLRADIUS,
1899 2*BALLRADIUS, 2*BALLRADIUS,
1900 (offset + 11520) % 23040, 11520);
1902 (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1903 MI_SCREEN(mi), s->divisions);
1908 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1910 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1912 const double sa = sin(s->angle);
1913 const double ca = cos(s->angle);
1934 static const XPoint club[] = {
1947 {-24, 2}, /* 0 close boundary */
1949 XPoint a[XtNumber(club)];
1951 /* Avoid wrapping */
1952 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1954 /* Translate and fake perspective */
1955 for(i = 0; i < XtNumber(club); i++) {
1956 a[i].x = (short)(MI_WIDTH(mi)/2 +
1957 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1958 club[i].y*sqrt(sp->scale)*ca);
1959 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1960 club[i].y*sa*sqrt(sp->scale));
1963 if(color != MI_BLACK_PIXEL(mi)) {
1964 /* Outline in black */
1965 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1966 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1967 LineSolid, CapRound, JoinRound);
1968 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1969 a, XtNumber(a), CoordModeOrigin);
1972 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1974 /* Don't be tempted to optimize erase by drawing all the black in
1975 one X operation. It must use the same ops as the colours to
1976 guarantee a clean erase. */
1978 i = 0; /* Colored stripes */
1979 p[i++] = a[1]; p[i++] = a[2];
1980 p[i++] = a[9]; p[i++] = a[10];
1981 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1982 p, i, Convex, CoordModeOrigin);
1984 p[i++] = a[3]; p[i++] = a[4];
1985 p[i++] = a[7]; p[i++] = a[8];
1986 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1987 p, i, Convex, CoordModeOrigin);
1989 if(color != MI_BLACK_PIXEL(mi)) {
1990 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1993 i = 0; /* White center band */
1994 p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
1995 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1996 p, i, Convex, CoordModeOrigin);
1998 i = 0; /* White handle */
1999 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2000 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2001 p, i, Convex, CoordModeOrigin);
2003 i = 0; /* White tip */
2004 p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2005 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2006 p, i, Convex, CoordModeOrigin);
2011 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2013 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2015 const double sa = sin(s->angle);
2016 const double ca = cos(s->angle);
2036 static const XPoint club[] = {
2047 {-24, 2}, /* 0 close boundary */
2049 XPoint a[XtNumber(club)];
2051 /* Avoid wrapping */
2052 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2054 /* Translate and fake perspective */
2055 for(i = 0; i < XtNumber(club); i++) {
2056 a[i].x = (short)(MI_WIDTH(mi)/2 +
2057 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2058 club[i].y*sqrt(sp->scale)*ca);
2059 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2060 club[i].y*sa*sqrt(sp->scale));
2063 if(color != MI_BLACK_PIXEL(mi)) {
2064 /* Outline in black */
2065 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2066 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2067 LineSolid, CapRound, JoinRound);
2068 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2069 a, XtNumber(a), CoordModeOrigin);
2072 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2074 /* Don't be tempted to optimize erase by drawing all the black in
2075 one X operation. It must use the same ops as the colours to
2076 guarantee a clean erase. */
2078 i = 0; /* Coloured center band */
2079 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2080 p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2081 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2082 p, i, Convex, CoordModeOrigin);
2084 if(color != MI_BLACK_PIXEL(mi)) {
2085 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2088 i = 0; /* White handle */
2089 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2090 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2091 p, i, Convex, CoordModeOrigin);
2093 i = 0; /* White tip */
2094 p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2095 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2096 p, i, Convex, CoordModeOrigin);
2101 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2103 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2104 XPoint head, tail, last;
2105 DXPoint dhead, dlast;
2106 const double sa = sin(s->angle);
2107 const double ca = cos(s->angle);
2109 const double TailLen = -24;
2110 const double HeadLen = 16;
2111 const short Width = (short)(5 * sqrt(sp->scale));
2123 dhead.x = s->x + HeadLen * PERSPEC * sa;
2124 dhead.y = s->y - HeadLen * ca;
2126 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2128 } else { /* Store 'last' so we can use it later when s->prev has
2130 if(s->prev != s->next) {
2131 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2132 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2139 /* Avoid wrapping (after last is stored) */
2140 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2142 head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2143 head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2145 last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2146 last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2148 tail.x = (short)(MI_WIDTH(mi)/2 +
2149 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2150 tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2152 if(color != MI_BLACK_PIXEL(mi)) {
2153 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2154 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2155 Width, LineSolid, CapRound, JoinRound);
2156 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2157 head.x, head.y, tail.x, tail.y);
2159 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2160 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2161 Width * 2, LineSolid, CapRound, JoinRound);
2163 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2164 head.x, head.y, last.x, last.y);
2169 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2171 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2173 const double sa = sin(s->angle);
2174 const double ca = cos(s->angle);
2185 static const XPoint knife[] = {
2193 XPoint a[XtNumber(knife)], p[5];
2195 /* Avoid wrapping */
2196 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2198 /* Translate and fake perspective */
2199 for(i = 0; i < XtNumber(knife); i++) {
2200 a[i].x = (short)(MI_WIDTH(mi)/2 +
2201 (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2202 knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2203 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2204 knife[i].y*sa*sqrt(sp->scale));
2208 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2209 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2210 LineSolid, CapRound, JoinRound);
2211 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2212 a[0].x, a[0].y, a[4].x, a[4].y);
2215 if(color != MI_BLACK_PIXEL(mi)) {
2216 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2219 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2220 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2221 p, i, Convex, CoordModeOrigin);
2225 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2227 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2228 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2229 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2230 double radius = 15 * sp->scale;
2231 short thickness = (short)(8 * sqrt(sp->scale));
2233 /* Avoid wrapping */
2234 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2236 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2237 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2238 thickness, LineSolid, CapRound, JoinRound);
2240 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2241 (short)(x - radius*PERSPEC), (short)(y - radius),
2242 (short)(2*radius*PERSPEC), (short)(2*radius),
2248 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2250 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2251 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2252 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2253 double radius = 12 * sp->scale;
2254 int offset = (int)(s->angle*64*180/M_PI);
2255 int holesize = (int)(3.0*sqrt(sp->scale));
2257 /* Avoid wrapping */
2258 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2260 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2261 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2262 (short)(x - radius), (short)(y - radius),
2263 (short)(2*radius), (short)(2*radius),
2265 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2266 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2267 LineSolid, CapRound, JoinRound);
2268 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2269 (short)(x - radius), (short)(y - radius),
2270 (short)(2*radius), (short)(2*radius),
2273 /* Draw finger holes */
2274 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2275 LineSolid, CapRound, JoinRound);
2277 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2278 (short)(x - radius*0.5), (short)(y - radius*0.5),
2279 (short)(2*radius*0.5), (short)(2*radius*0.5),
2280 (offset + 960) % 23040, 0);
2281 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2282 (short)(x - radius*0.7), (short)(y - radius*0.7),
2283 (short)(2*radius*0.7), (short)(2*radius*0.7),
2284 (offset + 1920) % 23040, 0);
2285 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2286 (short)(x - radius*0.7), (short)(y - radius*0.7),
2287 (short)(2*radius*0.7), (short)(2*radius*0.7),
2291 /**************************************************************************
2292 * Public Functions *
2294 **************************************************************************/
2298 release_juggle (ModeInfo * mi)
2300 if (juggles != NULL) {
2303 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2304 free_juggle(&juggles[screen]);
2306 juggles = (jugglestruct *) NULL;
2310 /* FIXME: refill_juggle currently just appends new throws to the
2311 * programme. This is fine if the programme is empty, but if there
2312 * are still some trajectories left then it really should take these
2316 refill_juggle(ModeInfo * mi)
2318 jugglestruct *sp = NULL;
2321 if (juggles == NULL)
2323 sp = &juggles[MI_SCREEN(mi)];
2325 /* generate pattern */
2326 if (pattern == NULL) {
2329 #define MAXREPEAT 300
2330 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2331 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2334 while (count < MI_CYCLES(mi)) {
2335 char buf[MAXPAT * 3 + 3], *b = buf;
2337 int l = NRAND(MAXPAT) + 1;
2338 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2340 { /* vary number of balls */
2341 int new_balls = sp->num_balls;
2344 if (new_balls == 2) /* Do not juggle 2 that often */
2345 change = NRAND(2 + CHANGE_BIAS / 4);
2347 change = NRAND(2 + CHANGE_BIAS);
2358 if (new_balls < sp->patternindex.minballs) {
2361 if (new_balls > sp->patternindex.maxballs) {
2364 if (new_balls < sp->num_balls) {
2365 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2368 sp->num_balls = new_balls;
2372 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2373 /* Pick from PortFolio */
2374 int p = sp->patternindex.index[sp->num_balls].start +
2375 NRAND(sp->patternindex.index[sp->num_balls].number);
2376 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2379 /* Invent a new pattern */
2381 for(i = 0; i < l; i++){
2383 do { /* Triangular Distribution => high values more likely */
2384 m = NRAND(sp->num_balls + 1);
2385 n = NRAND(sp->num_balls + 1);
2387 if (n == sp->num_balls) {
2390 switch(NRAND(5 + POSITION_BIAS)){
2391 case 0: /* Outside throw */
2393 case 1: /* Cross throw */
2395 case 2: /* Cross catch */
2397 case 3: /* Cross throw and catch */
2399 case 4: /* Bounce */
2402 break; /* Inside throw (default) */
2411 if (!program(mi, buf, NULL, t))
2416 } else { /* pattern supplied in height or 'a' notation */
2417 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2434 if (!projectile(sp)) {
2441 if(MI_IS_DEBUG(mi)) dump(sp);
2446 change_juggle(ModeInfo * mi)
2448 jugglestruct *sp = NULL;
2451 if (juggles == NULL)
2453 sp = &juggles[MI_SCREEN(mi)];
2455 /* Strip pending trajectories */
2456 for (t = sp->head->next; t != sp->head; t = t->next) {
2457 if(t->start > sp->time || t->finish < sp->time) {
2460 trajectory_destroy(n);
2464 /* Pick the current object theme */
2465 sp->objtypes = choose_object();
2469 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
2470 don't all those special effects. */
2471 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2473 show_figure(mi, MI_WHITE_PIXEL(mi), True);
2478 init_juggle (ModeInfo * mi)
2480 jugglestruct *sp = 0;
2483 if (juggles == NULL) { /* First-time initialisation */
2485 /* allocate jugglestruct */
2487 (jugglestruct *)calloc(MI_NUM_SCREENS(mi),
2488 sizeof (jugglestruct))) == NULL) {
2494 sp = &juggles[MI_SCREEN(mi)];
2496 if (only && *only && strcmp(only, " ")) {
2497 balls = clubs = torches = knives = rings = bballs = False;
2498 if (!strcasecmp (only, "balls")) balls = True;
2499 else if (!strcasecmp (only, "clubs")) clubs = True;
2500 else if (!strcasecmp (only, "torches")) torches = True;
2501 else if (!strcasecmp (only, "knives")) knives = True;
2502 else if (!strcasecmp (only, "rings")) rings = True;
2503 else if (!strcasecmp (only, "bballs")) bballs = True;
2505 (void) fprintf (stderr,
2506 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2507 "\t rings, or bballs (not \"%s\")\n", only);
2508 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2514 if (sp->head == 0) { /* first time initializing this juggler */
2516 sp->count = ABS(MI_COUNT(mi));
2520 /* record start time */
2521 sp->begintime = time(NULL);
2522 if(sp->patternindex.maxballs > 0) {
2523 sp->num_balls = sp->patternindex.minballs +
2524 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2527 show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
2528 information about the juggler's
2531 /* "7" should be about three times the height of the juggler's
2533 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2534 7 * THROW_CATCH_INTERVAL);
2536 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2537 balls = True; /* Have to juggle something! */
2539 /* create circular trajectory list */
2540 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2541 if(sp->head == NULL){
2546 /* create circular object list */
2547 ADD_ELEMENT(Object, sp->objects, sp->objects);
2548 if(sp->objects == NULL){
2553 /* create circular wander list */
2554 ADD_ELEMENT(Wander, sp->wander, sp->wander);
2555 if(sp->wander == NULL){
2559 (void)wander(sp, 0); /* Initialize wander */
2561 sp->pattern = strdup(""); /* Initialise saved pattern with
2565 sp = &juggles[MI_SCREEN(mi)];
2569 !strcasecmp (pattern, ".") ||
2570 !strcasecmp (pattern, "random")))
2573 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2574 /* pattern list needs indexing */
2575 int nelements = XtNumber(portfolio);
2578 /* sort according to number of balls */
2579 qsort((void*)portfolio, nelements,
2580 sizeof(portfolio[1]), compare_num_balls);
2582 /* last pattern has most balls */
2583 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2584 /* run through sorted list, indexing start of each group
2585 and number in group */
2586 sp->patternindex.maxballs = 1;
2587 for (i = 0; i < nelements; i++) {
2588 int b = get_num_balls(portfolio[i].pattern);
2589 if (b > sp->patternindex.maxballs) {
2590 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2591 if(numpat == 0) sp->patternindex.minballs = b;
2592 sp->patternindex.maxballs = b;
2594 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2599 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2602 /* Set up programme */
2605 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
2606 only be resizing and then we won't all those special effects. */
2607 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2609 /* Only put things here that won't interrupt the programme during
2612 /* Use MIN so that users can resize in interesting ways, eg
2613 narrow windows for tall patterns, etc */
2614 sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2616 if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */
2617 char *font = get_string_resource (MI_DISPLAY(mi), "font", "Font");
2618 sp->mode_font = XLoadQueryFont(MI_DISPLAY(mi), font);
2623 reshape_juggle (ModeInfo * mi, int width, int height)
2629 draw_juggle (ModeInfo * mi)
2631 Trajectory *traj = NULL;
2633 unsigned long future = 0;
2634 jugglestruct *sp = NULL;
2635 char *pattern = NULL;
2638 if (juggles == NULL)
2640 sp = &juggles[MI_SCREEN(mi)];
2642 MI_IS_DRAWN(mi) = True;
2645 /* Don't worry about flicker, trust Quartz's double-buffering.
2646 This is a fast fix for the pixel-turds I can't track down...
2648 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2654 (void)gettimeofday(&tv, NULL);
2655 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2657 sp->time += MI_DELAY(mi) / 1000;
2660 /* First pass: Move arms and strip out expired elements */
2661 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2662 if (traj->status != PREDICTOR) {
2663 /* Skip any elements that need further processing */
2664 /* We could remove them, but there shoudn't be many and they
2665 would be needed if we ever got the pattern refiller
2669 if (traj->start > future) { /* Lookahead to the end of the show */
2670 future = traj->start;
2672 if (sp->time < traj->start) { /* early */
2674 } else if (sp->time < traj->finish) { /* working */
2676 /* Look for pattern name */
2677 if(traj->pattern != NULL) {
2678 pattern=traj->pattern;
2681 if (traj->type == Empty || traj->type == Full) {
2682 /* Only interested in hands on this pass */
2683 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2684 double xd = 0, yd = 0;
2687 /* Find the catching offset */
2688 if(traj->object != NULL) {
2689 if(ObjectDefs[traj->object->type].handle > 0) {
2690 /* Handles Need to be oriented */
2691 xd = ObjectDefs[traj->object->type].handle *
2692 PERSPEC * sin(angle);
2693 yd = ObjectDefs[traj->object->type].handle *
2696 /* Balls are always caught at the bottom */
2701 p.x = (CUBIC(traj->xp, sp->time) - xd);
2702 p.y = (CUBIC(traj->yp, sp->time) + yd);
2703 reach_arm(mi, traj->hand, &p);
2705 /* Store updated hand position */
2709 if (traj->type == Ball || traj->type == Full) {
2710 /* Only interested in objects on this pass */
2714 if(traj->type == Full) {
2715 /* Adjusted these in the first pass */
2719 x = CUBIC(traj->xp, sp->time);
2720 y = CUBIC(traj->yp, sp->time);
2723 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2726 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2727 s->divisions = traj->divisions;
2728 traj->object->tracelen++;
2729 traj->object->active = True;
2731 } else { /* expired */
2732 Trajectory *n = traj;
2734 trajectory_destroy(n);
2738 /* Erase end of trails */
2739 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2741 for (s = o->trace->next;
2742 o->trace->next != o->trace &&
2743 (o->count == 0 || o->tracelen > o->tail);
2744 s = o->trace->next) {
2745 ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2748 if(o->count <= 0 && o->tracelen <= 0) {
2749 /* Object no longer in use and trail gone */
2754 if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2758 show_arms(mi, MI_BLACK_PIXEL(mi));
2759 cx = wander(sp, sp->time);
2760 /* Reduce flicker by only permitting movements of more than a pixel */
2761 if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2762 show_figure(mi, MI_BLACK_PIXEL(mi), False);
2766 show_figure(mi, MI_WHITE_PIXEL(mi), False);
2768 show_arms(mi, MI_WHITE_PIXEL(mi));
2771 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2773 ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2779 /* Save pattern name so we can erase it when it changes */
2780 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2781 /* Erase old name */
2782 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2784 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2785 0, 20, sp->pattern, strlen(sp->pattern));
2787 XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2788 0, 0, MI_WIDTH(mi), 25);
2791 sp->pattern = strdup(pattern);
2793 if (MI_IS_VERBOSE(mi)) {
2794 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2795 MI_SCREEN(mi), sp->pattern);
2798 if(sp->mode_font != None &&
2799 XTextWidth(sp->mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2800 /* Redraw once a cycle, in case it's obscured or it changed */
2801 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2802 XDrawImageString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2803 0, 20, sp->pattern, strlen(sp->pattern));
2807 if((int)(sp->time/10) % 1000 == 0)
2808 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2811 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2813 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2819 XSCREENSAVER_MODULE ("Juggle", juggle)
2821 #endif /* MODE_juggle */