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: -*-helvetica-bold-r-normal-*-180-*\n" \
139 "*fpsSolid: true\n" \
141 # define refresh_juggle 0
142 # define juggle_handle_event 0
143 # undef SMOOTH_COLORS
144 # include "xlockmore.h" /* in xscreensaver distribution */
145 #else /* STANDALONE */
146 # include "xlock.h" /* in xlockmore distribution */
147 #endif /* STANDALONE */
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", "release_juggle",
262 "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
263 10000, 200, 1000, 1, 64, 1.0, "",
264 "Shows a Juggler, juggling", 0, NULL
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(jugglestruct *sp) {
666 if (sp->head != NULL) {
667 while (sp->head->next != sp->head) {
668 trajectory_destroy(sp->head->next);
671 sp->head = (Trajectory *) NULL;
673 if(sp->objects != NULL) {
674 while (sp->objects->next != sp->objects) {
675 object_destroy(sp->objects->next);
678 sp->objects = (Object*)NULL;
680 if(sp->wander != NULL) {
681 while (sp->wander->next != sp->wander) {
682 Wander *w = sp->wander->next;
686 sp->wander = (Wander*)NULL;
688 if(sp->pattern != NULL) {
692 if (sp->mode_font!=None) {
693 XFreeFontInfo(NULL,sp->mode_font,1);
694 sp->mode_font = None;
699 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
703 ADD_ELEMENT(Trajectory, t, sp->head->prev);
704 if(t == NULL){ /* Out of Memory */
710 t->name = strdup(name);
723 /* add a Thratch to the performance */
725 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
727 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
733 if (MI_IS_VERBOSE(mi)) {
734 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
735 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
738 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
739 have been programmed */
740 /* title is the pattern name to be supplied to the first throw of
741 a sequence. If no name if given, use an empty title so that
742 the sequences are still delimited. */
743 const char *title = (name != NULL)? name : "";
748 for(p=patn; *p; p++) {
749 if (*p >= '0' && *p <='9') {
751 h = 10*h + (*p - '0');
753 Notation nn = notation;
755 case '[': /* begin Adam notation */
758 case '-': /* Inside throw */
761 case '+': /* Outside throw */
762 case '=': /* Cross throw */
763 case '&': /* Cross catch */
764 case 'x': /* Cross throw and catch */
765 case '_': /* Bounce */
766 case 'k': /* Kickup */
769 case '*': /* Lose ball */
773 case ']': /* end Adam notation */
779 if (!add_throw(sp, type, h, notation, title))
789 if(w == 0) { /* Only warn on first pass */
790 (void) fprintf(stderr,
791 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
798 if (seen) { /* end of sequence */
799 if (!add_throw(sp, type, h, notation, title))
813 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
815 4 4 1 3 12 2 4 1 4 4 13 0 3 1
818 #define BOUNCEOVER 10
822 /* Convert Adam notation into heights */
824 adam(jugglestruct *sp)
827 for(t = sp->head->next; t != sp->head; t = t->next) {
828 if (t->status == ATCH) {
831 for(p = t->next; a > 0; p = p->next) {
833 t->height = -9; /* Indicate end of processing for name() */
836 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
841 if(t->height > BOUNCEOVER && t->posn == ' '){
842 t->posn = '_'; /* high defaults can be bounced */
843 } else if(t->height < 3 && t->posn == '_') {
844 t->posn = ' '; /* Can't bounce short throws. */
846 if(t->height < KICKMIN && t->posn == 'k'){
847 t->posn = ' '; /* Can't kick short throws */
849 if(t->height > THROWMAX){
850 t->posn = 'k'; /* Use kicks for ridiculously high throws */
857 /* Discover converted heights and update the sequence title */
859 name(jugglestruct *sp)
864 for(t = sp->head->next; t != sp->head; t = t->next) {
865 if (t->status == THRATCH && t->name != NULL) {
867 for(p = t; p == t || p->name == NULL; p = p->next) {
868 if(p == sp->head || p->height < 0) { /* end of reliable data */
872 b += sprintf(b, " %d", p->height);
874 b += sprintf(b, " %c%d", p->posn, p->height);
876 if(b - buffer > 500) break; /* otherwise this could eventually
877 overflow. It'll be too big to
881 (void) sprintf(b, ", %s", t->name);
883 free(t->name); /* Don't need name any more, it's been converted
886 if(t->pattern != NULL) free(t->pattern);
887 t->pattern = strdup(buffer);
892 /* Split Thratch notation into explicit throws and catches.
893 Usually Catch follows Throw in same hand, but take care of special
896 /* ..n1.. -> .. LTn RT1 LC RC .. */
897 /* ..nm.. -> .. LTn LC RTm RC .. */
900 part(jugglestruct *sp)
902 Trajectory *t, *nt, *p;
903 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
905 for (t = sp->head->next; t != sp->head; t = t->next) {
906 if (t->status > THRATCH) {
908 } else if (t->status == THRATCH) {
911 /* plausibility check */
912 if (t->height <= 2 && t->posn == '_') {
913 t->posn = ' '; /* no short bounces */
915 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
916 t->posn = ' '; /* 1's need close catches */
921 case ' ': posn = '-'; t->posn = '+'; break;
922 case '+': posn = '+'; t->posn = '-'; break;
923 case '=': posn = '='; t->posn = '+'; break;
924 case '&': posn = '+'; t->posn = '='; break;
925 case 'x': posn = '='; t->posn = '='; break;
926 case '_': posn = '_'; t->posn = '-'; break;
927 case 'k': posn = 'k'; t->posn = 'k'; break;
929 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
932 hand = (Hand) ((hand + 1) % 2);
937 if (t->height == 1 && p != sp->head) {
938 p = p->prev; /* '1's are thrown earlier than usual */
944 ADD_ELEMENT(Trajectory, nt, p);
952 nt->height = t->height;
962 choose_object(void) {
965 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
966 if(balls && o == BALL) break;
967 if(clubs && o == CLUB) break;
968 if(torches && o == TORCH) break;
969 if(knives && o == KNIFE) break;
970 if(rings && o == RING) break;
971 if(bballs && o == BBALLS) break;
976 /* Connnect up throws and catches to figure out which ball goes where.
977 Do the same with the juggler's hands. */
982 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
985 for (t = sp->head->next; t != sp->head; t = t->next) {
986 if (t->status == ACTION) {
987 if (t->action == THROW) {
988 if (t->type == Empty) {
989 /* Create new Object */
990 ADD_ELEMENT(Object, t->object, sp->objects);
991 t->object->count = 1;
992 t->object->tracelen = 0;
993 t->object->active = False;
994 /* Initialise object's circular trace list */
995 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
997 if (MI_NPIXELS(mi) > 2) {
998 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
1001 t->object->color = 1;
1003 t->object->color = 0;
1007 /* Small chance of picking a random object instead of the
1009 if(NRAND(OBJMIXPROB) == 0) {
1010 t->object->type = choose_object();
1012 t->object->type = sp->objtypes;
1015 /* Check to see if we need trails for this object */
1016 if(tail < ObjectDefs[t->object->type].mintrail) {
1017 t->object->tail = ObjectDefs[t->object->type].mintrail;
1019 t->object->tail = tail;
1023 /* Balls can change divisions at each throw */
1024 t->divisions = 2 * (NRAND(2) + 1);
1026 /* search forward for next catch in this hand */
1027 for (p = t->next; t->handlink == NULL; p = p->next) {
1028 if(p->status < ACTION || p == sp->head) return;
1029 if (p->action == CATCH) {
1030 if (t->handlink == NULL && p->hand == t->hand) {
1036 if (t->height > 0) {
1039 /* search forward for next ball catch */
1040 for (p = t->next; t->balllink == NULL; p = p->next) {
1041 if(p->status < ACTION || p == sp->head) {
1045 if (p->action == CATCH) {
1046 if (t->balllink == NULL && --h < 1) { /* caught */
1047 t->balllink = p; /* complete trajectory */
1049 if (p->type == Full) {
1050 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1051 MI_SCREEN(mi), t->object->color);
1055 DUP_OBJECT(p, t); /* accept catch */
1056 p->angle = t->angle;
1057 p->divisions = t->divisions;
1062 t->type = Empty; /* thrown */
1063 } else if (t->action == CATCH) {
1064 /* search forward for next throw from this hand */
1065 for (p = t->next; t->handlink == NULL; p = p->next) {
1066 if(p->status < ACTION || p == sp->head) return;
1067 if (p->action == THROW && p->hand == t->hand) {
1068 p->type = t->type; /* pass ball */
1069 DUP_OBJECT(p, t); /* pass object */
1070 p->divisions = t->divisions;
1075 t->status = LINKEDACTION;
1080 /* Clap when both hands are empty */
1082 clap(jugglestruct *sp)
1085 for (t = sp->head->next; t != sp->head; t = t->next) {
1086 if (t->status == LINKEDACTION &&
1087 t->action == CATCH &&
1089 t->handlink != NULL &&
1090 t->handlink->height == 0) { /* Completely idle hand */
1092 for (p = t->next; p != sp->head; p = p->next) {
1093 if (p->status == LINKEDACTION &&
1094 p->action == CATCH &&
1095 p->hand != t->hand) { /* Next catch other hand */
1096 if(p->type == Empty &&
1097 p->handlink != NULL &&
1098 p->handlink->height == 0) { /* Also completely idle */
1100 t->handlink->posn = '^'; /* Move first hand's empty throw */
1101 p->posn = '^'; /* to meet second hand's empty
1105 break; /* Only need first catch */
1112 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1114 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1115 with velocity dx1 at time t1 */
1117 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1126 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1127 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1132 s.c = (3*a*t0 - 2*b)*t0 + c;
1133 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1137 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1138 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1139 velocity dx2 at time t2. The arrival and departure velocities at
1140 x1, t1 must be the same. */
1142 makeSplinePair(Spline *s1, Spline *s2,
1143 double x0, double dx0, int t0,
1145 double x2, double dx2, int t2)
1147 double x10, x21, t21, t10, t20, dx1;
1153 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1154 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1156 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1157 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1161 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1162 from x at time t at constant velocity dx. sy goes from y at time t
1163 with velocity dy and constant acceleration g. */
1165 makeParabola(Trajectory *n,
1166 double x, double dx, double y, double dy, double g)
1168 double t = (double)n->start;
1172 n->xp.d = -dx*t + x;
1175 n->yp.c = -g*t + dy;
1176 n->yp.d = g/2*t*t - dy*t + y;
1181 /* Make juggler wander around the screen */
1182 static double wander(jugglestruct *sp, unsigned long time)
1185 for (w = sp->wander->next; w != sp->wander; w = w->next) {
1186 if (w->finish < sp->time) { /* expired */
1190 } else if(w->finish > time) {
1194 if(w == sp->wander) { /* Need a new one */
1195 ADD_ELEMENT(Wander, w, sp->wander->prev);
1196 if(w == NULL) { /* Memory problem */
1199 w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1203 w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1205 w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1207 return CUBIC(w->s, time);
1210 #define SX 25 /* Shoulder Width */
1212 /* Convert hand position symbols into actual time/space coordinates */
1214 positions(jugglestruct *sp)
1217 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1218 for (t = sp->head->next; t != sp->head; t = t->next) {
1219 if (t->status >= PTHRATCH) {
1221 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1222 /* Allow ACTIONs to be annotated, but we won't mark them ready
1223 for the next stage */
1230 if (t->action == CATCH) { /* Throw-to-catch */
1231 if (t->type == Empty) {
1232 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1233 } else { /* successful catch */
1234 now += (int)(THROW_CATCH_INTERVAL);
1236 } else { /* Catch-to-throw */
1237 if(t->object != NULL) {
1238 now += (int) (CATCH_THROW_INTERVAL *
1239 ObjectDefs[t->object->type].weight);
1241 now += (int) (CATCH_THROW_INTERVAL);
1247 else /* Concatenated performances may need clock resync */
1250 t->cx = wander(sp, t->start);
1255 /* Add room for the handle */
1256 if(t->action == CATCH && t->object != NULL)
1257 yo -= ObjectDefs[t->object->type].handle;
1260 case '-': xo = sx - pose; break;
1263 case '+': xo = sx + pose; break;
1265 case '=': xo = - sx - pose; yo += pose; break;
1266 case '^': xo = 0; yo += pose*2; break; /* clap */
1268 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1272 t->angle = (((t->hand == LEFT) ^
1273 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1276 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1279 /* Only mark complete if it was already linked */
1280 if(t->status == LINKEDACTION) {
1281 t->status = PTHRATCH;
1288 /* Private physics functions */
1290 /* Compute the spin-rate for a trajectory. Different types of throw
1291 (eg, regular thows, bounces, kicks, etc) have different spin
1294 type = type of object
1295 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1296 old = earlier spin to consider
1297 dt = time span of this trajectory
1298 height = height of ball throw or 0 if based on old spin
1299 turns = full club turns required during this operation
1300 togo = partial club turns required to match hands
1303 spinrate(ObjType type, Trajectory *h, double old, double dt,
1304 int height, int turns, double togo)
1306 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1308 if(ObjectDefs[type].handle != 0) { /* Clubs */
1309 return (dir * turns * 2 * M_PI + togo) / dt;
1310 } else if(height == 0) { /* Balls already spinning */
1312 } else { /* Balls */
1313 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1318 /* compute the angle at the end of a spinning trajectory */
1320 end_spin(Trajectory *t)
1322 return t->angle + t->spin * (t->finish - t->start);
1325 /* Sets the initial angle of the catch following hand movement t to
1326 the final angle of the throw n. Also sets the angle of the
1327 subsequent throw to the same angle plus half a turn. */
1329 match_spins_on_catch(Trajectory *t, Trajectory *n)
1331 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1332 t->balllink->angle = end_spin(n);
1333 if(t->balllink->handlink != NULL) {
1334 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1340 find_bounce(jugglestruct *sp,
1341 double yo, double yf, double yc, double tc, double cor)
1343 double tb, i, dy = 0;
1344 const double e = 1; /* permissible error in yc */
1348 yt = height at catch time after one bounce
1349 one or three roots according to timing
1350 find one by interval bisection
1353 for(i = tc / 2; i > 0.0001; i/=2){
1356 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1359 dy = (yf - yo)/tb + sp->Gr/2*tb;
1361 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1364 }else if(yt > yc - e){
1370 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1377 new_predictor(const Trajectory *t, int start, int finish, double angle)
1380 ADD_ELEMENT(Trajectory, n, t->prev);
1385 n->divisions = t->divisions;
1387 n->status = PREDICTOR;
1395 /* Turn abstract timings into physically appropriate object trajectories. */
1397 projectile(jugglestruct *sp)
1400 const int yf = 0; /* Floor height */
1402 for (t = sp->head->next; t != sp->head; t = t->next) {
1403 if (t->status != PTHRATCH || t->action != THROW) {
1405 } else if (t->balllink == NULL) { /* Zero Throw */
1406 t->status = BPREDICTOR;
1407 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1409 } else if(t->balllink == t->handlink) {
1410 /* '2' height - hold on to ball. Don't need to consider
1411 flourishes, 'hands' will do that automatically anyway */
1414 /* Zero spin to avoid wrist injuries */
1416 match_spins_on_catch(t, t);
1418 t->status = BPREDICTOR;
1421 if (t->posn == '_') { /* Bounce once */
1423 const int tb = t->start +
1424 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1425 (double) (t->balllink->start - t->start),
1426 ObjectDefs[t->object->type].cor);
1428 if(tb < t->start) { /* bounce too hard */
1429 t->posn = '+'; /* Use regular throw */
1431 Trajectory *n; /* First (throw) trajectory. */
1432 double dt; /* Time span of a trajectory */
1433 double dy; /* Distance span of a follow-on trajectory.
1434 First trajectory uses t->dy */
1435 /* dx is constant across both trajectories */
1436 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1438 { /* ball follows parabola down */
1439 n = new_predictor(t, t->start, tb, t->angle);
1440 if(n == NULL) return False;
1441 dt = n->finish - n->start;
1442 /* Ball rate 4, no flight or matching club turns */
1443 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1444 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1445 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1448 { /* ball follows parabola up */
1449 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1451 if(m == NULL) return False;
1452 dt = m->finish - m->start;
1453 /* Use previous ball rate, no flight club turns */
1454 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1455 t->balllink->angle - m->angle);
1456 match_spins_on_catch(t, m);
1457 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1458 makeParabola(m, t->balllink->x - t->dx * dt,
1459 t->dx, (double) yf, dy, sp->Gr);
1462 t->status = BPREDICTOR;
1465 } else if (t->posn == 'k') { /* Drop & Kick */
1466 Trajectory *n; /* First (drop) trajectory. */
1467 Trajectory *o; /* Second (rest) trajectory */
1468 Trajectory *m; /* Third (kick) trajectory */
1469 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1470 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1473 { /* Fall to ground */
1474 n = new_predictor(t, t->start, td, t->angle);
1475 if(n == NULL) return False;
1476 dt = n->finish - n->start;
1477 /* Ball spin rate 4, no flight club turns */
1478 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1479 t->balllink->angle - n->angle);
1480 t->dx = (t->balllink->x - t->x) / dt;
1481 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1482 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1485 { /* Rest on ground */
1486 o = new_predictor(t, n->finish, tk, end_spin(n));
1487 if(o == NULL) return False;
1489 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1494 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1495 if(m == NULL) return False;
1496 dt = m->finish - m->start;
1497 /* Match receiving hand, ball rate 4, one flight club turn */
1498 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1499 4, 1, t->balllink->angle - m->angle);
1500 match_spins_on_catch(t, m);
1501 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1502 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1505 t->status = BPREDICTOR;
1509 /* Regular flight, no bounce */
1510 { /* ball follows parabola */
1512 Trajectory *n = new_predictor(t, t->start,
1513 t->balllink->start, t->angle);
1514 if(n == NULL) return False;
1515 dt = t->balllink->start - t->start;
1517 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1518 t->balllink->angle - n->angle);
1519 match_spins_on_catch(t, n);
1520 t->dx = (t->balllink->x - t->x) / dt;
1521 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1522 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1525 t->status = BPREDICTOR;
1531 /* Turn abstract hand motions into cubic splines. */
1533 hands(jugglestruct *sp)
1535 Trajectory *t, *u, *v;
1537 for (t = sp->head->next; t != sp->head; t = t->next) {
1538 /* no throw => no velocity */
1539 if (t->status != BPREDICTOR) {
1544 if (u == NULL) { /* no next catch */
1548 if (v == NULL) { /* no next throw */
1552 /* double spline takes hand from throw, thru catch, to
1555 t->finish = u->start;
1556 t->status = PREDICTOR;
1558 u->finish = v->start;
1559 u->status = PREDICTOR;
1562 /* FIXME: These adjustments leave a small glitch when alternating
1563 balls and clubs. Just hope no-one notices. :-) */
1565 /* make sure empty hand spin matches the thrown object in case it
1568 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1569 fabs((u->angle - t->angle)/(u->start - t->start));
1571 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1572 fabs((v->angle - u->angle)/(v->start - u->start));
1574 (void) makeSplinePair(&t->xp, &u->xp,
1575 t->x, t->dx, t->start,
1577 v->x, v->dx, v->start);
1578 (void) makeSplinePair(&t->yp, &u->yp,
1579 t->y, t->dy, t->start,
1581 v->y, v->dy, v->start);
1583 t->status = PREDICTOR;
1587 /* Given target x, y find_elbow puts hand at target if possible,
1588 * otherwise makes hand point to the target */
1590 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1594 double x = p->x - s->x;
1595 double y = p->y - s->y;
1596 h2 = x*x + y*y + z*z;
1597 if (h2 > 4 * armlength * armlength) {
1598 t = armlength/sqrt(h2);
1601 h->x = 2 * t * x + s->x;
1602 h->y = 2 * t * y + s->y;
1604 r = sqrt((double)(x*x + z*z));
1605 t = sqrt(4 * armlength * armlength / h2 - 1);
1606 e->x = x*(1 + y*t/r)/2 + s->x;
1607 e->y = (y - r*t)/2 + s->y;
1614 /* NOTE: returned x, y adjusted for arm reach */
1616 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1618 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1620 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1621 *p = sp->arm[1][side][HAND] = h;
1622 sp->arm[1][side][ELBOW] = e;
1626 /* dumps a human-readable rendition of the current state of the juggle
1627 pipeline to stderr for debugging */
1629 dump(jugglestruct *sp)
1632 for (t = sp->head->next; t != sp->head; t = t->next) {
1633 switch (t->status) {
1635 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1638 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1639 t->pattern == NULL?"":t->pattern);
1642 if (t->action == CATCH)
1643 (void) fprintf(stderr, "%p A %c%cC\n",
1645 t->hand ? 'R' : 'L');
1647 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1649 t->hand ? 'R' : 'L',
1650 (t->action == THROW)?'T':'N',
1654 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1657 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1658 t->height, t->object == NULL?0:t->object->color,
1659 (void*)t->handlink, (void*)t->balllink);
1662 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1665 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1666 t->height, t->type, t->object == NULL?0:t->object->color,
1667 t->start, t->finish);
1670 (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1671 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1672 t->object == NULL?0:t->object->color,
1673 t->start, t->finish, t->yp.c);
1676 (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1677 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1678 t->object == NULL?0:t->object->color,
1679 t->start, t->finish, t->yp.c);
1682 (void) fprintf(stderr, "%p: status %d not implemented\n",
1683 (void*)t, t->status);
1687 (void) fprintf(stderr, "---\n");
1691 static int get_num_balls(const char *j)
1697 for (p = j; *p; p++) {
1698 if (*p >= '0' && *p <='9') { /* digit */
1699 h = 10*h + (*p - '0');
1715 compare_num_balls(const void *p1, const void *p2)
1718 i = get_num_balls(((patternstruct*)p1)->pattern);
1719 j = get_num_balls(((patternstruct*)p2)->pattern);
1734 /**************************************************************************
1735 * Rendering Functions *
1737 **************************************************************************/
1740 show_arms(ModeInfo * mi, unsigned long color)
1742 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1745 XPoint a[XtNumber(sp->arm[0][0])];
1746 if(color == MI_BLACK_PIXEL(mi)) {
1751 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1752 ARMWIDTH, LineSolid, CapRound, JoinRound);
1753 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1754 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1755 /* Translate into device coords */
1756 for(i = 0; i < XtNumber(a); i++) {
1757 a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1758 a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
1760 sp->arm[0][side][i] = sp->arm[1][side][i];
1762 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1763 a, XtNumber(a), CoordModeOrigin);
1768 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1770 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1791 static const XPoint figure[] = {
1792 { 15, 70}, /* 0 Left Hip */
1793 { 0, 90}, /* 1 Waist */
1794 { SX, 130}, /* 2 Left Shoulder */
1795 {-SX, 130}, /* 3 Right Shoulder */
1796 {-15, 70}, /* 4 Right Hip */
1797 { 0, 130}, /* 5 Neck */
1798 { 0, 140}, /* 6 Chin */
1799 { SX, 0}, /* 7 Left Foot */
1800 {-SX, 0}, /* 8 Right Foot */
1801 {-17, 174}, /* 9 Head1 */
1802 { 17, 140}, /* 10 Head2 */
1804 XPoint a[XtNumber(figure)];
1806 /* Translate into device coords */
1807 for(i = 0; i < XtNumber(figure); i++) {
1808 a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1809 a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1812 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1813 ARMWIDTH, LineSolid, CapRound, JoinRound);
1814 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1817 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1818 p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1819 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1820 p, i, CoordModeOrigin);
1823 p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1824 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1825 p, i, CoordModeOrigin);
1828 p[i++] = a[5]; p[i++] = a[6];
1829 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1830 p, i, CoordModeOrigin);
1833 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1835 a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1836 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1837 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1839 /* Initialise arms */
1841 for(i = 0; i < 2; i++){
1842 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1843 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1844 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1845 sp->arm[i][LEFT][HAND].x = figure[0].x;
1846 sp->arm[i][LEFT][HAND].y = figure[1].y;
1847 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1848 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1849 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1850 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1851 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1857 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1859 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1860 int offset = (int)(s->angle*64*180/M_PI);
1861 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1862 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1864 /* Avoid wrapping */
1865 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1867 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1868 if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) {
1869 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1870 x - BALLRADIUS, y - BALLRADIUS,
1871 2*BALLRADIUS, 2*BALLRADIUS,
1873 } else if (s->divisions == 4) { /* 90 degree divisions */
1874 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1875 x - BALLRADIUS, y - BALLRADIUS,
1876 2*BALLRADIUS, 2*BALLRADIUS,
1877 offset % 23040, 5760);
1878 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1879 x - BALLRADIUS, y - BALLRADIUS,
1880 2*BALLRADIUS, 2*BALLRADIUS,
1881 (offset + 11520) % 23040, 5760);
1883 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1884 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1885 x - BALLRADIUS, y - BALLRADIUS,
1886 2*BALLRADIUS, 2*BALLRADIUS,
1887 (offset + 5760) % 23040, 5760);
1888 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1889 x - BALLRADIUS, y - BALLRADIUS,
1890 2*BALLRADIUS, 2*BALLRADIUS,
1891 (offset + 17280) % 23040, 5760);
1892 } else if (s->divisions == 2) { /* 180 degree divisions */
1893 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1894 x - BALLRADIUS, y - BALLRADIUS,
1895 2*BALLRADIUS, 2*BALLRADIUS,
1896 offset % 23040, 11520);
1898 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1899 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1900 x - BALLRADIUS, y - BALLRADIUS,
1901 2*BALLRADIUS, 2*BALLRADIUS,
1902 (offset + 11520) % 23040, 11520);
1904 (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1905 MI_SCREEN(mi), s->divisions);
1910 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1912 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1914 const double sa = sin(s->angle);
1915 const double ca = cos(s->angle);
1936 static const XPoint club[] = {
1949 {-24, 2}, /* 0 close boundary */
1951 XPoint a[XtNumber(club)];
1953 /* Avoid wrapping */
1954 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1956 /* Translate and fake perspective */
1957 for(i = 0; i < XtNumber(club); i++) {
1958 a[i].x = (short)(MI_WIDTH(mi)/2 +
1959 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1960 club[i].y*sqrt(sp->scale)*ca);
1961 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1962 club[i].y*sa*sqrt(sp->scale));
1965 if(color != MI_BLACK_PIXEL(mi)) {
1966 /* Outline in black */
1967 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1968 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1969 LineSolid, CapRound, JoinRound);
1970 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1971 a, XtNumber(a), CoordModeOrigin);
1974 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1976 /* Don't be tempted to optimize erase by drawing all the black in
1977 one X operation. It must use the same ops as the colours to
1978 guarantee a clean erase. */
1980 i = 0; /* Colored stripes */
1981 p[i++] = a[1]; p[i++] = a[2];
1982 p[i++] = a[9]; p[i++] = a[10];
1983 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1984 p, i, Convex, CoordModeOrigin);
1986 p[i++] = a[3]; p[i++] = a[4];
1987 p[i++] = a[7]; p[i++] = a[8];
1988 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1989 p, i, Convex, CoordModeOrigin);
1991 if(color != MI_BLACK_PIXEL(mi)) {
1992 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1995 i = 0; /* White center band */
1996 p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
1997 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1998 p, i, Convex, CoordModeOrigin);
2000 i = 0; /* White handle */
2001 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2002 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2003 p, i, Convex, CoordModeOrigin);
2005 i = 0; /* White tip */
2006 p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2007 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2008 p, i, Convex, CoordModeOrigin);
2013 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2015 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2017 const double sa = sin(s->angle);
2018 const double ca = cos(s->angle);
2038 static const XPoint club[] = {
2049 {-24, 2}, /* 0 close boundary */
2051 XPoint a[XtNumber(club)];
2053 /* Avoid wrapping */
2054 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2056 /* Translate and fake perspective */
2057 for(i = 0; i < XtNumber(club); i++) {
2058 a[i].x = (short)(MI_WIDTH(mi)/2 +
2059 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2060 club[i].y*sqrt(sp->scale)*ca);
2061 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2062 club[i].y*sa*sqrt(sp->scale));
2065 if(color != MI_BLACK_PIXEL(mi)) {
2066 /* Outline in black */
2067 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2068 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2069 LineSolid, CapRound, JoinRound);
2070 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2071 a, XtNumber(a), CoordModeOrigin);
2074 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2076 /* Don't be tempted to optimize erase by drawing all the black in
2077 one X operation. It must use the same ops as the colours to
2078 guarantee a clean erase. */
2080 i = 0; /* Coloured center band */
2081 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2082 p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2083 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2084 p, i, Convex, CoordModeOrigin);
2086 if(color != MI_BLACK_PIXEL(mi)) {
2087 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2090 i = 0; /* White handle */
2091 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2092 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2093 p, i, Convex, CoordModeOrigin);
2095 i = 0; /* White tip */
2096 p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2097 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2098 p, i, Convex, CoordModeOrigin);
2103 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2105 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2106 XPoint head, tail, last;
2107 DXPoint dhead, dlast;
2108 const double sa = sin(s->angle);
2109 const double ca = cos(s->angle);
2111 const double TailLen = -24;
2112 const double HeadLen = 16;
2113 const short Width = (short)(5 * sqrt(sp->scale));
2125 dhead.x = s->x + HeadLen * PERSPEC * sa;
2126 dhead.y = s->y - HeadLen * ca;
2128 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2130 } else { /* Store 'last' so we can use it later when s->prev has
2132 if(s->prev != s->next) {
2133 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2134 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2141 /* Avoid wrapping (after last is stored) */
2142 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2144 head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2145 head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2147 last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2148 last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2150 tail.x = (short)(MI_WIDTH(mi)/2 +
2151 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2152 tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2154 if(color != MI_BLACK_PIXEL(mi)) {
2155 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2156 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2157 Width, LineSolid, CapRound, JoinRound);
2158 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2159 head.x, head.y, tail.x, tail.y);
2161 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2162 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2163 Width * 2, LineSolid, CapRound, JoinRound);
2165 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2166 head.x, head.y, last.x, last.y);
2171 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2173 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2175 const double sa = sin(s->angle);
2176 const double ca = cos(s->angle);
2187 static const XPoint knife[] = {
2195 XPoint a[XtNumber(knife)], p[5];
2197 /* Avoid wrapping */
2198 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2200 /* Translate and fake perspective */
2201 for(i = 0; i < XtNumber(knife); i++) {
2202 a[i].x = (short)(MI_WIDTH(mi)/2 +
2203 (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2204 knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2205 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2206 knife[i].y*sa*sqrt(sp->scale));
2210 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2211 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2212 LineSolid, CapRound, JoinRound);
2213 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2214 a[0].x, a[0].y, a[4].x, a[4].y);
2217 if(color != MI_BLACK_PIXEL(mi)) {
2218 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2221 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2222 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2223 p, i, Convex, CoordModeOrigin);
2227 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2229 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2230 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2231 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2232 double radius = 15 * sp->scale;
2233 short thickness = (short)(8 * sqrt(sp->scale));
2235 /* Avoid wrapping */
2236 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2238 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2239 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2240 thickness, LineSolid, CapRound, JoinRound);
2242 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2243 (short)(x - radius*PERSPEC), (short)(y - radius),
2244 (short)(2*radius*PERSPEC), (short)(2*radius),
2250 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2252 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2253 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2254 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2255 double radius = 12 * sp->scale;
2256 int offset = (int)(s->angle*64*180/M_PI);
2257 int holesize = (int)(3.0*sqrt(sp->scale));
2259 /* Avoid wrapping */
2260 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2262 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2263 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2264 (short)(x - radius), (short)(y - radius),
2265 (short)(2*radius), (short)(2*radius),
2267 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2268 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2269 LineSolid, CapRound, JoinRound);
2270 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2271 (short)(x - radius), (short)(y - radius),
2272 (short)(2*radius), (short)(2*radius),
2275 /* Draw finger holes */
2276 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2277 LineSolid, CapRound, JoinRound);
2279 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2280 (short)(x - radius*0.5), (short)(y - radius*0.5),
2281 (short)(2*radius*0.5), (short)(2*radius*0.5),
2282 (offset + 960) % 23040, 0);
2283 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2284 (short)(x - radius*0.7), (short)(y - radius*0.7),
2285 (short)(2*radius*0.7), (short)(2*radius*0.7),
2286 (offset + 1920) % 23040, 0);
2287 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2288 (short)(x - radius*0.7), (short)(y - radius*0.7),
2289 (short)(2*radius*0.7), (short)(2*radius*0.7),
2293 /**************************************************************************
2294 * Public Functions *
2296 **************************************************************************/
2300 release_juggle (ModeInfo * mi)
2302 if (juggles != NULL) {
2305 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2306 free_juggle(&juggles[screen]);
2308 juggles = (jugglestruct *) NULL;
2312 /* FIXME: refill_juggle currently just appends new throws to the
2313 * programme. This is fine if the programme is empty, but if there
2314 * are still some trajectories left then it really should take these
2318 refill_juggle(ModeInfo * mi)
2320 jugglestruct *sp = NULL;
2323 if (juggles == NULL)
2325 sp = &juggles[MI_SCREEN(mi)];
2327 /* generate pattern */
2328 if (pattern == NULL) {
2331 #define MAXREPEAT 300
2332 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2333 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2336 while (count < MI_CYCLES(mi)) {
2337 char buf[MAXPAT * 3 + 3], *b = buf;
2339 int l = NRAND(MAXPAT) + 1;
2340 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2342 { /* vary number of balls */
2343 int new_balls = sp->num_balls;
2346 if (new_balls == 2) /* Do not juggle 2 that often */
2347 change = NRAND(2 + CHANGE_BIAS / 4);
2349 change = NRAND(2 + CHANGE_BIAS);
2360 if (new_balls < sp->patternindex.minballs) {
2363 if (new_balls > sp->patternindex.maxballs) {
2366 if (new_balls < sp->num_balls) {
2367 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2370 sp->num_balls = new_balls;
2374 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2375 /* Pick from PortFolio */
2376 int p = sp->patternindex.index[sp->num_balls].start +
2377 NRAND(sp->patternindex.index[sp->num_balls].number);
2378 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2381 /* Invent a new pattern */
2383 for(i = 0; i < l; i++){
2385 do { /* Triangular Distribution => high values more likely */
2386 m = NRAND(sp->num_balls + 1);
2387 n = NRAND(sp->num_balls + 1);
2389 if (n == sp->num_balls) {
2392 switch(NRAND(5 + POSITION_BIAS)){
2393 case 0: /* Outside throw */
2395 case 1: /* Cross throw */
2397 case 2: /* Cross catch */
2399 case 3: /* Cross throw and catch */
2401 case 4: /* Bounce */
2404 break; /* Inside throw (default) */
2413 if (!program(mi, buf, NULL, t))
2418 } else { /* pattern supplied in height or 'a' notation */
2419 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2436 if (!projectile(sp)) {
2443 if(MI_IS_DEBUG(mi)) dump(sp);
2448 change_juggle(ModeInfo * mi)
2450 jugglestruct *sp = NULL;
2453 if (juggles == NULL)
2455 sp = &juggles[MI_SCREEN(mi)];
2457 /* Strip pending trajectories */
2458 for (t = sp->head->next; t != sp->head; t = t->next) {
2459 if(t->start > sp->time || t->finish < sp->time) {
2462 trajectory_destroy(n);
2466 /* Pick the current object theme */
2467 sp->objtypes = choose_object();
2471 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
2472 don't all those special effects. */
2473 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2475 show_figure(mi, MI_WHITE_PIXEL(mi), True);
2480 init_juggle (ModeInfo * mi)
2482 jugglestruct *sp = 0;
2485 if (juggles == NULL) { /* First-time initialisation */
2487 /* allocate jugglestruct */
2489 (jugglestruct *)calloc(MI_NUM_SCREENS(mi),
2490 sizeof (jugglestruct))) == NULL) {
2496 sp = &juggles[MI_SCREEN(mi)];
2498 if (only && *only && strcmp(only, " ")) {
2499 balls = clubs = torches = knives = rings = bballs = False;
2500 if (!strcasecmp (only, "balls")) balls = True;
2501 else if (!strcasecmp (only, "clubs")) clubs = True;
2502 else if (!strcasecmp (only, "torches")) torches = True;
2503 else if (!strcasecmp (only, "knives")) knives = True;
2504 else if (!strcasecmp (only, "rings")) rings = True;
2505 else if (!strcasecmp (only, "bballs")) bballs = True;
2507 (void) fprintf (stderr,
2508 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2509 "\t rings, or bballs (not \"%s\")\n", only);
2510 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2516 if (sp->head == 0) { /* first time initializing this juggler */
2518 sp->count = ABS(MI_COUNT(mi));
2522 /* record start time */
2523 sp->begintime = time(NULL);
2524 if(sp->patternindex.maxballs > 0) {
2525 sp->num_balls = sp->patternindex.minballs +
2526 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2529 show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
2530 information about the juggler's
2533 /* "7" should be about three times the height of the juggler's
2535 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2536 7 * THROW_CATCH_INTERVAL);
2538 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2539 balls = True; /* Have to juggle something! */
2541 /* create circular trajectory list */
2542 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2543 if(sp->head == NULL){
2548 /* create circular object list */
2549 ADD_ELEMENT(Object, sp->objects, sp->objects);
2550 if(sp->objects == NULL){
2555 /* create circular wander list */
2556 ADD_ELEMENT(Wander, sp->wander, sp->wander);
2557 if(sp->wander == NULL){
2561 (void)wander(sp, 0); /* Initialize wander */
2563 sp->pattern = strdup(""); /* Initialise saved pattern with
2567 sp = &juggles[MI_SCREEN(mi)];
2571 !strcasecmp (pattern, ".") ||
2572 !strcasecmp (pattern, "random")))
2575 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2576 /* pattern list needs indexing */
2577 int nelements = XtNumber(portfolio);
2580 /* sort according to number of balls */
2581 qsort((void*)portfolio, nelements,
2582 sizeof(portfolio[1]), compare_num_balls);
2584 /* last pattern has most balls */
2585 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2586 /* run through sorted list, indexing start of each group
2587 and number in group */
2588 sp->patternindex.maxballs = 1;
2589 for (i = 0; i < nelements; i++) {
2590 int b = get_num_balls(portfolio[i].pattern);
2591 if (b > sp->patternindex.maxballs) {
2592 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2593 if(numpat == 0) sp->patternindex.minballs = b;
2594 sp->patternindex.maxballs = b;
2596 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2601 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2604 /* Set up programme */
2607 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
2608 only be resizing and then we won't all those special effects. */
2609 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2611 /* Only put things here that won't interrupt the programme during
2614 /* Use MIN so that users can resize in interesting ways, eg
2615 narrow windows for tall patterns, etc */
2616 sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2618 if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */
2619 char *font = get_string_resource (MI_DISPLAY(mi), "font", "Font");
2620 sp->mode_font = XLoadQueryFont(MI_DISPLAY(mi), font);
2625 reshape_juggle (ModeInfo * mi, int width, int height)
2631 draw_juggle (ModeInfo * mi)
2633 Trajectory *traj = NULL;
2635 unsigned long future = 0;
2636 jugglestruct *sp = NULL;
2637 char *pattern = NULL;
2640 if (juggles == NULL)
2642 sp = &juggles[MI_SCREEN(mi)];
2644 MI_IS_DRAWN(mi) = True;
2647 /* Don't worry about flicker, trust Quartz's double-buffering.
2648 This is a fast fix for the pixel-turds I can't track down...
2650 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2656 (void)gettimeofday(&tv, NULL);
2657 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2659 sp->time += MI_DELAY(mi) / 1000;
2662 /* First pass: Move arms and strip out expired elements */
2663 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2664 if (traj->status != PREDICTOR) {
2665 /* Skip any elements that need further processing */
2666 /* We could remove them, but there shoudn't be many and they
2667 would be needed if we ever got the pattern refiller
2671 if (traj->start > future) { /* Lookahead to the end of the show */
2672 future = traj->start;
2674 if (sp->time < traj->start) { /* early */
2676 } else if (sp->time < traj->finish) { /* working */
2678 /* Look for pattern name */
2679 if(traj->pattern != NULL) {
2680 pattern=traj->pattern;
2683 if (traj->type == Empty || traj->type == Full) {
2684 /* Only interested in hands on this pass */
2685 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2686 double xd = 0, yd = 0;
2689 /* Find the catching offset */
2690 if(traj->object != NULL) {
2691 if(ObjectDefs[traj->object->type].handle > 0) {
2692 /* Handles Need to be oriented */
2693 xd = ObjectDefs[traj->object->type].handle *
2694 PERSPEC * sin(angle);
2695 yd = ObjectDefs[traj->object->type].handle *
2698 /* Balls are always caught at the bottom */
2703 p.x = (CUBIC(traj->xp, sp->time) - xd);
2704 p.y = (CUBIC(traj->yp, sp->time) + yd);
2705 reach_arm(mi, traj->hand, &p);
2707 /* Store updated hand position */
2711 if (traj->type == Ball || traj->type == Full) {
2712 /* Only interested in objects on this pass */
2716 if(traj->type == Full) {
2717 /* Adjusted these in the first pass */
2721 x = CUBIC(traj->xp, sp->time);
2722 y = CUBIC(traj->yp, sp->time);
2725 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2728 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2729 s->divisions = traj->divisions;
2730 traj->object->tracelen++;
2731 traj->object->active = True;
2733 } else { /* expired */
2734 Trajectory *n = traj;
2736 trajectory_destroy(n);
2740 /* Erase end of trails */
2741 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2743 for (s = o->trace->next;
2744 o->trace->next != o->trace &&
2745 (o->count == 0 || o->tracelen > o->tail);
2746 s = o->trace->next) {
2747 ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2750 if(o->count <= 0 && o->tracelen <= 0) {
2751 /* Object no longer in use and trail gone */
2756 if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2760 show_arms(mi, MI_BLACK_PIXEL(mi));
2761 cx = wander(sp, sp->time);
2762 /* Reduce flicker by only permitting movements of more than a pixel */
2763 if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2764 show_figure(mi, MI_BLACK_PIXEL(mi), False);
2768 show_figure(mi, MI_WHITE_PIXEL(mi), False);
2770 show_arms(mi, MI_WHITE_PIXEL(mi));
2773 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2775 ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2781 /* Save pattern name so we can erase it when it changes */
2782 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2783 /* Erase old name */
2784 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2786 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2787 0, 20, sp->pattern, strlen(sp->pattern));
2789 XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2790 0, 0, MI_WIDTH(mi), 25);
2793 sp->pattern = strdup(pattern);
2795 if (MI_IS_VERBOSE(mi)) {
2796 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2797 MI_SCREEN(mi), sp->pattern);
2800 if(sp->mode_font != None &&
2801 XTextWidth(sp->mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2802 /* Redraw once a cycle, in case it's obscured or it changed */
2803 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2804 XDrawImageString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2805 0, 20, sp->pattern, strlen(sp->pattern));
2809 if((int)(sp->time/10) % 1000 == 0)
2810 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2813 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2815 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2821 XSCREENSAVER_MODULE ("Juggle", juggle)
2823 #endif /* MODE_juggle */