1 /* -*- Mode: C; tab-width: 4 -*- */
5 static const char sccsid[] = "@(#)juggle.c 5.00 2000/11/01 xlockmore";
9 * Copyright (c) 1996 by Tim Auckland <Tim.Auckland@Procket.com>
11 * Permission to use, copy, modify, and distribute this software and its
12 * documentation for any purpose and without fee is hereby granted,
13 * provided that the above copyright notice appear in all copies and that
14 * both that copyright notice and this permission notice appear in
15 * supporting documentation.
17 * This file is provided AS IS with no warranties of any kind. The author
18 * shall have no liability with respect to the infringement of copyrights,
19 * trade secrets or any patents by this file or any part thereof. In no
20 * event will the author be liable for any lost revenue or profits or
21 * other special, indirect and consequential damages.
24 * 01-Nov-2000: Allocation checks
30 * Fix timing to run at approx same speed on all machines.
31 * Store shorter pattern and refill when required.
32 * Use -cycles and -count in a rational manner.
33 * Merge pattern selector with pattern generator.
35 * Clap when all the balls are in the air
40 Notes on Adam Chalcraft Juggling Notation (used by permission)
41 a-> Adam's notation s-> Site swap (Cambridge) notation
43 To define a map from a-notation to s-notation
44 ("site-swap"), both of which look like doubly infinite sequences of natural
45 numbers. In s-notation, there is a restriction on what is allowed, namely
46 for the sequence s_n, the associated function f(n)=n+s_n must be a
47 bijection. In a-notation, there is no restriction.
49 To go from a-notation to s-notation, you start by mapping each a_n to a
50 permutation of N, the natural numbers.
53 1 -> (10) [i.e. f(1)=0, f(0)=1]
54 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
55 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
58 Then for each n, you look at how long 0 takes to get back to 0 again and
59 you call this t_n. If a_n=0, for example, then since the identity leaves 0
60 alone, it gets back to 0 in 1 step, so t_n=1. If a_n=1, then f(0)=1. Now any
61 further a_n=0 leave 1 alone, but the next a_n>0 sends 1 back to 0. Hence t_n
62 is 2 + the number of 0's following the 1. Finally, set s_n = t_n - 1.
64 To give some examples, it helps to have a notation for cyclic sequences. By
65 (123), for example, I mean ...123123123123... . Now under the a-notation ->
66 s-notation mapping we have some familiar examples:
68 (0)->(0), (1)->(1), (2)->(2) etc.
69 (21)->(31), (31)->(51), (41)->(71) etc.
70 (10)->(20), (20)->(40), (30)->(60) etc.
71 (331)->(441), (312)->(612), (303)->(504), (321)->(531)
72 (43)->(53), (434)->(534), (433)->(633)
75 In general, the number of balls is the *average* of the s-notation, and the
76 *maximum* of the a-notation. Another theorem is that the minimum values in
77 the a-notation and the s-notation and equal, and preserved in the same
80 The usefulness of a-notation is the fact that there are no restrictions on
81 what is allowed. This makes random juggle generation much easier. It also
82 makes enumeration very easy. Another handy feature is computing changes.
83 Suppose you can do (5) and want a neat change up to (771) in s-notation
84 [Mike Day actually needed this example!]. Write them both in a-notation,
85 which gives (5) and (551). Now concatenate them (in general, there may be
86 more than one way to do this, but not in this example), to get
87 ...55555555551551551551551...
88 Now convert back to s-notation, to get
89 ...55555566771771771771771...
90 So the answer is to do two 6 throws and then go straight into (771).
91 Coming back down of course,
92 ...5515515515515515555555555...
94 ...7717717717716615555555555...
95 so the answer is to do a single 661 and then drop straight down to (5).
97 [The number of balls in the generated pattern occasionally changes. In
98 order to decrease the number of balls I had to introduce a new symbol
99 into the Adam notation, [*] which means 'lose the current ball'.]
104 #define PROGCLASS "Juggle"
105 #define HACK_INIT init_juggle
106 #define HACK_DRAW draw_juggle
107 #define juggle_opts xlockmore_opts
108 #define DEFAULTS "*delay: 10000 \n" \
112 #define SMOOTH_COLORS
113 #include "xlockmore.h" /* in xscreensaver distribution */
114 #else /* STANDALONE */
115 #include "xlock.h" /* in xlockmore distribution */
116 #endif /* STANDALONE */
120 #define DEF_PATTERN "." /* All patterns */
121 #define DEF_TRAIL "0" /* No trace */
123 #define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */
125 #define DEF_SOLID "FALSE" /* Not solid */
127 static char *pattern;
134 static XrmOptionDescRec opts[] =
136 {(char* ) "-pattern", (char *) ".juggle.pattern",
137 XrmoptionSepArg, (caddr_t) NULL},
138 {(char* ) "-trail", (char *) ".juggle.trail",
139 XrmoptionSepArg, (caddr_t) NULL},
141 {(char *) "-uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "on"},
142 {(char *) "+uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "off"},
144 {(char *) "-solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "on"},
145 {(char *) "+solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "off"}
147 static argtype vars[] =
149 {(caddr_t *) &pattern, (char *) "pattern",
150 (char *) "Pattern", (char *) DEF_PATTERN, t_String},
151 {(caddr_t *) &trail, (char *) "trail",
152 (char *) "Trail", (char *) DEF_TRAIL, t_Int},
154 {(caddr_t *) &uni, (char *) "uni",
155 (char *) "Uni", (char *) DEF_UNI, t_Bool},
157 {(caddr_t *) &solid, (char *) "solid",
158 (char *) "Solid", (char *) DEF_SOLID, t_Bool}
160 static OptionStruct desc[] =
162 {(char *) "-pattern string", (char *) "Cambridge Juggling Pattern"},
163 {(char *) "-trail num", (char *) "Trace Juggling Patterns"},
165 {(char *) "-/+uni", (char *) "Unicycle"},
167 {(char *) "-/+solid", (char *) "solid color (else its a 4 panel look (half white))"}
170 ModeSpecOpt juggle_opts =
171 {sizeof opts / sizeof opts[0], opts,
172 sizeof vars / sizeof vars[0], vars, desc};
175 ModStruct juggle_description = {
176 "juggle", "init_juggle", "draw_juggle", "release_juggle",
177 "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
178 10000, 150, 30, 1, 64, 1.0, "",
179 "Shows a Juggler, juggling", 0, NULL
185 #include <X11/unix_time.h>
189 #include <sys/time.h>
191 #if HAVE_SYS_SELECT_H
192 #include <sys/select.h>
197 #define ARMLENGTH ((int) (40.0 * sp->scale))
198 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
199 #define POSE ((int) (10.0 * sp->scale))
200 #define SX ((int) (25.0 * sp->scale))
201 #define SZ ((int) (25.0 * sp->scale))
202 #define SY ((int) (25.0 * sp->scale))
203 #define HIPY ((int) (85.0 * sp->scale))
204 #define RHIPX ((int) (-15.0 * sp->scale))
205 #define LHIPX ((int) (15.0 * sp->scale))
206 #define RFX ((int) (-25.0 * sp->scale))
207 #define LFX ((int) (25.0 * sp->scale))
208 #define FY ((int) (155.0 * sp->scale))
209 #define WSTY ((int) (65.0 * sp->scale))
210 #define NEY ((int) (15.0 * sp->scale))
211 #define HED ((int) (35.0 * sp->scale))
212 #define BALLRADIUS ARMWIDTH
215 #define TRACE_LENGTH 50
216 #define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */
221 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
224 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
226 #define THROW_CATCH_INTERVAL (sp->count)
227 #define THROW_NULL_INTERVAL (sp->count * 0.5)
228 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
229 #define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */
234 typedef enum {HEIGHT, ADAM} Notation;
235 typedef enum {Empty, Full, Ball} Throwable;
236 typedef enum {LEFT, RIGHT} Hand;
237 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
238 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
239 PREDICTOR} TrajectoryStatus;
241 typedef struct trajectory *TrajectoryPtr;
243 typedef struct {double a, b, c, d; } Spline;
245 typedef struct trajectory {
246 TrajectoryPtr prev, next; /* for building list */
247 TrajectoryStatus status;
261 double degree_offset;
262 TrajectoryPtr balllink;
263 TrajectoryPtr handlink;
267 double dx; /* initial velocity */
274 int x, y; /* current position */
289 XPoint figure_path[FIGURE1];
290 XSegment figure_segs[FIGURE2];
295 time_t begintime; /* seconds */
296 int time; /* millisecond timer */
300 static jugglestruct *juggles = (jugglestruct *) NULL;
315 static PatternIndex* patternindex = (PatternIndex *) NULL;
317 /* List of popular patterns, in any order */
318 static patternstruct portfolio[] = {
319 {(char *) "[+2 1]", (char *) "+3 1, Typical 2 ball juggler"},
320 {(char *) "[2 0]", (char *) "4 0, 2 balls 1 hand"},
321 {(char *) "[2 0 1]", (char *) "5 0 1"},
322 {(char *) "[+2 0 +2 0 0]", (char *) "+5 0 +5 0 0"},
323 {(char *) "[3]", (char *) "3, cascade"},
324 {(char *) "[+3]", (char *) "+3, reverse cascade"},
325 {(char *) "[=3]", (char *) "=3, cascade under arm"},
326 {(char *) "[&3]", (char *) "&3, cascade catching under arm"},
327 {(char *) "[_3]", (char *) "_3, bouncing cascade"},
328 {(char *) "[+3 x3 =3]", (char *) "+3 x3 =3, Mill's mess"},
329 {(char *) "[3 2 1]", (char *) "5 3 1"},
330 {(char *) "[3 3 1]", (char *) "4 4 1"},
331 {(char *) "[3 1 2]", (char *) "6 1 2, See-saw"},
332 {(char *) "[=3 3 1 2]", (char *) "=4 5 1 2"},
333 {(char *) "[=3 2 2 3 1 2]", (char *) "=6 2 2 5 1 2, =4 5 1 2 stretched"},
334 {(char *) "[+3 3 1 3]", (char *) "+4 4 1 3, anemic shower box"},
335 {(char *) "[3 3 1]", (char *) "4 4 1"},
336 {(char *) "[+3 2 3]", (char *) "+4 2 3"},
337 {(char *) "[+3 1]", (char *) "+5 1, 3 shower"},
338 {(char *) "[_3 1]", (char *) "_5 1, bouncing 3 shower"},
339 {(char *) "[3 0 3 0 3]", (char *) "5 0 5 0 5, shake 3 out of 5"},
340 {(char *) "[3 3 3 0 0]", (char *) "5 5 5 0 0, flash 3 out of 5"},
341 {(char *) "[3 3 0]", (char *) "4 5 0, complete waste of a 5 ball juggler"},
342 {(char *) "[3 3 3 0 0 0 0]", (char *) "7 7 7 0 0 0 0, 3 flash"},
343 {(char *) "[+3 0 +3 0 +3 0 0]", (char *) "+7 0 +7 0 +7 0 0"},
344 {(char *) "[4]", (char *) "4, 4 cascade"},
345 {(char *) "[+4 3]", (char *) "+5 3, 4 ball half shower"},
346 {(char *) "[4 4 2]", (char *) "5 5 2"},
347 {(char *) "[+4 4 4 +4]", (char *) "+4 4 4 +4, 4 columns"},
348 {(char *) "[4 3 +4]", (char *) "5 3 +4"},
349 {(char *) "[+4 1]", (char *) "+7 1, 4 shower"},
350 {(char *) "[4 4 4 4 0]", (char *) "5 5 5 5 0, learning 5"},
351 {(char *) "[5]", (char *) "5, 5 cascade"},
352 {(char *) "[_5 _5 _5 _5 _5 5 5 5 5 5]", (char *) "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
353 {(char *) "[+5 x5 =5]", (char *) "+5 x5 =5, Mill's mess for 5"},
354 {(char *) "[6]", (char *) "6, 6 cascade"},
355 {(char *) "[7]", (char *) "7, 7 cascade"},
356 {(char *) "[_7]", (char *) "_7, bouncing 7 cascade"},
359 /* Private Functions */
361 /* list management */
363 /* t receives trajectory to be created. ot must point to an existing
364 trajectory or be identical to t to start a new list. */
365 #define INSERT_AFTER_TOP(t, ot) \
366 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
367 {free_juggle(sp); return;} \
368 (t)->next = (ot)->next; \
371 (t)->next->prev = (t)
372 #define INSERT_AFTER(t, ot) \
373 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
374 {free_juggle(sp); return False;} \
375 (t)->next = (ot)->next; \
378 (t)->next->prev = (t)
381 /* t must point to an existing trajectory. t must not be an
382 expression ending ->next or ->prev */
384 (t)->next->prev = (t)->prev; \
385 (t)->prev->next = (t)->next; \
386 (void) free((void *) t)
389 free_pattern(jugglestruct *sp) {
390 if (sp->head != NULL) {
391 while (sp->head->next != sp->head) {
392 Trajectory *t = sp->head->next;
394 REMOVE(t); /* don't eliminate t */
396 (void) free((void *) sp->head);
397 sp->head = (Trajectory *) NULL;
402 free_juggle(jugglestruct *sp)
404 if (sp->trace != NULL) {
405 (void) free((void *) sp->trace);
406 sp->trace = (XPoint *) NULL;
412 add_throw(jugglestruct *sp, char type, int h, Notation n)
416 INSERT_AFTER(t, sp->head->prev);
428 /* add a Thratch to the performance */
430 program(ModeInfo *mi, const char *patn, int repeat)
432 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
438 if (MI_IS_VERBOSE(mi)) {
439 (void) fprintf(stderr, "%s x %d\n", patn, repeat);
442 for(i=0; i < repeat; i++) {
447 for(p=patn; *p; p++) {
448 if (*p >= '0' && *p <='9') {
450 h = 10*h + (*p - '0');
452 Notation nn = notation;
454 case '[': /* begin Adam notation */
457 case '-': /* Inside throw */
458 case '+': /* Outside throw */
459 case '=': /* Cross throw */
460 case '&': /* Cross catch */
461 case 'x': /* Cross throw and catch */
462 case '_': /* Bounce */
465 case '*': /* Lose ball */
469 case ']': /* end Adam notation */
474 if (!add_throw(sp, type, h, notation))
483 (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
489 if (!add_throw(sp, type, h, notation))
504 4 4 1 3 12 2 4 1 4 4 13 0 3 1
507 #define BOUNCEOVER 10
510 adam(jugglestruct *sp)
513 for(t = sp->head->next; t != sp->head; t = t->next) {
514 if (t->status == ATCH) {
517 for(p = t->next; a > 0 && p != sp->head; p = p->next) {
518 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
523 if(t->height > BOUNCEOVER && t->posn == ' '){
524 t->posn = '_'; /* high defaults can be bounced */
528 (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
534 /* Split Thratch notation into explicit throws and catches.
535 Usually Catch follows Throw in same hand, but take care of special
538 /* ..n1.. -> .. LTn RT1 LC RC .. */
539 /* ..nm.. -> .. LTn LC RTm RC .. */
542 part(jugglestruct *sp)
544 Trajectory *t, *nt, *p;
545 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
547 for (t = sp->head->next; t != sp->head; t = t->next) {
548 if (t->status > THRATCH) {
550 } else if (t->status == THRATCH) {
553 /* plausibility check */
554 if (t->height <= 2 && t->posn == '_') {
555 t->posn = ' '; /* no short bounces */
557 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
558 t->posn = ' '; /* 1's need close catches */
563 case ' ': /* fall through */
564 case '-': posn = '-'; t->posn = '+'; break;
565 case '+': posn = '+'; t->posn = '-'; break;
566 case '=': posn = '='; t->posn = '+'; break;
567 case '&': posn = '+'; t->posn = '='; break;
568 case 'x': posn = '='; t->posn = '='; break;
569 case '_': posn = '_'; t->posn = '-'; break;
570 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
572 hand = (Hand) ((hand + 1) % 2);
577 if (t->height == 1) {
578 p = p->prev; /* early throw */
584 nt->height = t->height;
592 /* Connnect up throws and catches to figure out which ball goes where.
593 Do the same with the juggler's hands. */
598 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
601 for (t = sp->head->next; t != sp->head; t = t->next) {
602 if (t->status == ACTION) {
604 (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
607 (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
610 if (t->action == THROW) {
611 if (t->type == Empty) {
612 if (MI_NPIXELS(mi) > 2) {
613 t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
615 t->spin = NRAND(5) - 2;
616 t->degree_offset = NRAND(360);
617 t->divisions = 2 * ((LRAND() & 1) + 1);
620 /* search forward for next hand catch */
621 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
622 if (p->action == CATCH) {
623 if (t->handlink == NULL && p->hand == t->hand) {
624 t->handlink = p; /* next catch in this hand */
632 /* search forward for next ball catch */
633 for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
634 if (p->action == CATCH) {
635 if (t->balllink == NULL && --h < 1) { /* caught */
637 if (p->type == Full) {
641 t->balllink = p; /* complete trajectory */
643 p->color = t->color; /* accept catch */
645 p->degree_offset = t->degree_offset;
646 p->divisions = t->divisions;
651 t->type = Empty; /* thrown */
652 } else if (t->action == CATCH) {
653 /* search forward for next throw from this hand */
654 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
655 if (p->action == THROW && p->hand == t->hand) {
656 p->type = t->type; /* pass ball */
657 p->color = t->color; /* pass color */
658 p->spin = NRAND(5) - 2;
659 p->degree_offset = NRAND(360);
660 p->divisions = 2 * ((LRAND() & 1) + 1);
665 t->status = LINKEDACTION;
670 /* Convert hand position symbols into actual time/space coordinates */
672 positions(jugglestruct *sp)
676 for (t = sp->head->next; t != sp->head; t = t->next) {
677 if (t->status == PTHRATCH) {
679 } else if (t->status == LINKEDACTION) {
683 if (t->action == CATCH) {
684 if (t->type == Empty) {
685 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
687 now += THROW_CATCH_INTERVAL; /* successful catch */
690 now += (int) CATCH_THROW_INTERVAL; /* throws are very short */
697 case '-': xo = SX - POSE; break;
699 case '+': xo = SX + POSE; break;
700 case '=': xo = - SX - POSE; yo += 2 * POSE; break;
701 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
703 t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
706 t->status = PTHRATCH;
712 /* Private physics functions */
715 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
724 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
725 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
730 s.c = (3*a*t0 - 2*b)*t0 + c;
731 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
736 makeSplinePair(Spline *s1, Spline *s2,
737 int x0, double dx0, int t0,
739 int x2, double dx2, int t2)
742 double t21, t10, t20, dx1;
748 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
749 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
751 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
752 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
756 /* Turn abstract timings into physically appropriate ball trajectories. */
758 projectile(jugglestruct *sp)
761 for (t = sp->head->next; t != sp->head; t = t->next) {
762 if (t->status != PTHRATCH) {
765 if (t->action == THROW) {
766 if (t->balllink != NULL) {
767 if (t->posn == '_') { /* Bounce once */
768 double tc, y0, yf, yc, tb, e, i;
770 tc = t->balllink->start - t->start;
775 e = 1; /* permissible error in yc */
779 yt = height at catch time after one bounce
780 one or three roots according to timing
781 find one by interval bisection
784 for(i = tc / 2; i > 0; i/=2){
787 (void) fprintf(stderr, "div by zero!\n");
790 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
792 yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
795 }else if(yt < yc - e){
805 t->dx = (t->balllink->x - t->x) / tc;
807 /* ball follows parabola down */
808 INSERT_AFTER(n, t->prev);
810 n->finish = (int) (t->start + tb);
814 n->degree_offset = t->degree_offset;
815 n->divisions = t->divisions;
816 n->status = PREDICTOR;
818 t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
820 /* Represent parabola as a degenerate spline -
821 linear in x, quadratic in y */
825 n->xp.d = -t->dx*t0 + t->x;
828 n->yp.c = -sp->Gr*t0 + t->dy;
829 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
832 /* ball follows parabola up */
833 INSERT_AFTER(n, t->prev);
834 n->start = (int) (t0 + tb);
835 n->finish = (int) (t0 + tc);
839 n->degree_offset = t->degree_offset;
840 n->divisions = t->divisions;
841 n->status = PREDICTOR;
846 n->xp.d = -t->dx*t0 + t->x;
848 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
850 /* Represent parabola as a degenerate spline -
851 linear in x, quadratic in y */
854 n->yp.c = -sp->Gr*t0 - COR*dy;
855 n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
858 t->status = BPREDICTOR;
863 /* ball follows parabola */
864 INSERT_AFTER(n, t->prev);
866 n->finish = t->balllink->start;
870 n->degree_offset = t->degree_offset;
871 n->divisions = t->divisions;
872 n->status = PREDICTOR;
875 dt = t->balllink->start - t->start;
876 t->dx = (t->balllink->x - t->x) / dt;
877 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
879 /* Represent parabola as a degenerate spline -
880 linear in x, quadratic in y */
884 n->xp.d = -t->dx*t0 + t->x;
887 n->yp.c = -sp->Gr*t0 + t->dy;
888 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
891 t->status = BPREDICTOR;
893 } else { /* Zero Throw */
894 t->status = BPREDICTOR;
901 /* Turn abstract hand motions into cubic splines. */
903 hands(jugglestruct *sp)
905 Trajectory *t, *u, *v;
906 for (t = sp->head->next; t != sp->head; t = t->next) {
907 /* no throw => no velocity */
908 if (t->status != BPREDICTOR) {
913 if (u == NULL) { /* no next catch */
917 if (v == NULL) { /* no next throw */
921 /* double spline takes hand from throw, thru catch, to
924 t->finish = u->start;
925 t->status = PREDICTOR;
927 u->finish = v->start;
928 u->status = PREDICTOR;
930 (void) makeSplinePair(&t->xp, &u->xp,
931 t->x, t->dx, t->start,
933 v->x, v->dx, v->start);
934 (void) makeSplinePair(&t->yp, &u->yp,
935 t->y, t->dy, t->start,
937 v->y, v->dy, v->start);
939 t->status = PREDICTOR;
943 /* Given target x, y find_elbow puts hand at target if possible,
944 * otherwise makes hand point to the target */
946 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
950 h2 = x*x + y*y + z*z;
951 if (h2 > 4*ARMLENGTH*ARMLENGTH) {
952 t = ARMLENGTH/sqrt(h2);
953 e->x = (short) (t*x);
954 e->y = (short) (t*y);
958 r = sqrt((double)(x*x + z*z));
959 t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
960 e->x = (short) (x*(1 - y*t/r)/2);
961 e->y = (short) ((y + r*t)/2);
967 /* NOTE: returned x, y adjusted for arm reach */
969 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
971 Display *dpy = MI_DISPLAY(mi);
972 Window win = MI_WINDOW(mi);
974 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
976 int sig = (side == LEFT) ? 1 : -1;
978 XSetLineAttributes(dpy, gc,
979 ARMWIDTH, LineSolid, CapRound, JoinRound);
980 if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
982 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
983 find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
984 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
985 *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
986 *y = sp->arm[side][0].y = sp->cy + SY + h.y;
987 sp->arm[side][1].x = sp->cx + sig*SX + e.x;
988 sp->arm[side][1].y = sp->cy + SY + e.y;
990 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
991 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
992 XSetLineAttributes(dpy, gc,
993 1, LineSolid, CapNotLast, JoinRound);
997 draw_figure(ModeInfo * mi)
999 Display *dpy = MI_DISPLAY(mi);
1000 Window win = MI_WINDOW(mi);
1002 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1004 XSetLineAttributes(dpy, gc,
1005 ARMWIDTH, LineSolid, CapRound, JoinRound);
1006 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1007 XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1008 XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1009 XDrawArc(dpy, win, gc,
1010 sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1011 XSetLineAttributes(dpy, gc,
1012 1, LineSolid, CapNotLast, JoinRound);
1016 /* dumps a human-readable rendition of the current state of the juggle
1017 pipeline to stderr for debugging */
1020 dump(jugglestruct *sp)
1024 for (t = sp->head->next; t != sp->head; t = t->next) {
1025 switch (t->status) {
1027 (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1030 (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1032 t->hand ? 'R' : 'L',
1033 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1037 (void) fprintf(stderr, "L %c%c%c%d %d\n",
1040 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1041 t->height, t->color);
1044 (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1046 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1047 t->height, t->type, t->color,
1048 t->start, t->finish);
1051 (void) fprintf(stderr, "P %c %2d %6d %6d %g\n",
1052 t->type == Ball?'b':t->type == Empty?'e':'f',
1054 t->start, t->finish, t->yp.c);
1057 (void) fprintf(stderr, "status %d not implemented\n", t->status);
1064 static int get_num_balls(const char *j)
1069 for (p = j; *p; p++) {
1070 if (*p >= '0' && *p <='9') { /* digit */
1071 h = 10*h + (*p - '0');
1086 static int compare_num_balls(const void *p1, const void *p2)
1088 int i = get_num_balls(((patternstruct*)p1)->pattern);
1089 int j = get_num_balls(((patternstruct*)p2)->pattern);
1103 /* Public functions */
1106 release_juggle(ModeInfo * mi)
1108 if (juggles != NULL) {
1111 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1112 free_juggle(&juggles[screen]);
1113 (void) free((void *) juggles);
1114 juggles = (jugglestruct *) NULL;
1116 if (patternindex != NULL) {
1117 (void) free((void *) patternindex);
1118 patternindex = (PatternIndex *) NULL;
1123 init_juggle(ModeInfo * mi)
1127 XPoint figure1[FIGURE1];
1128 XSegment figure2[FIGURE2];
1129 if (pattern != NULL && *pattern == '.') {
1132 if (pattern == NULL && patternindex == NULL) {
1133 /* pattern list needs indexing */
1134 int nelements = sizeof(portfolio)/sizeof(patternstruct);
1138 /* sort according to number of balls */
1139 qsort((void*)portfolio, nelements,
1140 sizeof(patternstruct), compare_num_balls);
1142 /* last pattern has most balls */
1143 maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1144 /* allocate index */
1145 if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1146 sizeof (PatternIndex))) == NULL) {
1150 /* run through sorted list, indexing start of each group
1151 and number in group */
1153 for (i = 0; i < nelements; i++) {
1154 int b = get_num_balls(portfolio[i].pattern);
1156 if (MI_IS_VERBOSE(mi)) {
1157 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1158 numpat, maxballs, (numpat == 1) ? "" : "s");
1160 patternindex[maxballs].number = numpat;
1163 patternindex[maxballs].start = i;
1168 if (MI_IS_VERBOSE(mi)) {
1169 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1170 numpat, maxballs, (numpat == 1) ? "" : "s");
1172 patternindex[maxballs].number = numpat;
1175 if (juggles == NULL) { /* allocate jugglestruct */
1176 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1177 sizeof (jugglestruct))) == NULL) {
1182 sp = &juggles[MI_SCREEN(mi)];
1186 if (MI_IS_FULLRANDOM(mi)) {
1187 sp->solid = (Bool) (LRAND() & 1);
1189 sp->uni = (Bool) (LRAND() & 1);
1198 sp->width = MI_WIDTH(mi);
1199 sp->height = MI_HEIGHT(mi);
1200 sp->count = ABS(MI_COUNT(mi));
1203 sp->scale = sp->height / 480.0;
1204 /* vary x a little so the juggler does not burn the screen */
1205 sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1206 sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1207 /* "7" hits top of screen */
1208 sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1210 figure1[0].x = LHIPX, figure1[0].y = HIPY;
1211 figure1[1].x = 0, figure1[1].y = WSTY;
1212 figure1[2].x = SX, figure1[2].y = SY;
1213 figure1[3].x = -SX, figure1[3].y = SY;
1214 figure1[4].x = 0, figure1[4].y = WSTY;
1215 figure1[5].x = RHIPX, figure1[5].y = HIPY;
1216 figure1[6].x = LHIPX, figure1[6].y = HIPY;
1217 figure2[0].x1 = 0, figure2[0].y1 = SY,
1218 figure2[0].x2 = 0, figure2[0].y2 = NEY;
1219 figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1220 figure2[1].x2 = LFX, figure2[1].y2 = FY;
1221 figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1222 figure2[2].x2 = RFX, figure2[2].y2 = FY;
1225 for (i = 0; i < FIGURE1; i++) {
1226 sp->figure_path[i].x = figure1[i].x + sp->cx;
1227 sp->figure_path[i].y = figure1[i].y + sp->cy;
1230 for (i = 0; i < FIGURE2; i++) {
1231 sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1232 sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1233 sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1234 sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1237 sp->arm[LEFT][2].x = sp->cx + SX;
1238 sp->arm[LEFT][2].y = sp->cy + SY;
1239 sp->arm[RIGHT][2].x = sp->cx - SX;
1240 sp->arm[RIGHT][2].y = sp->cy + SY;
1242 if (sp->trace == NULL) {
1243 if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1249 /* Clear the background. */
1254 /* record start time */
1255 sp->begintime = time(NULL);
1259 /* create circular list */
1260 INSERT_AFTER_TOP(sp->head, sp->head);
1262 /* generate pattern */
1263 if (pattern == NULL) {
1266 #define MAXREPEAT 30
1267 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1268 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1271 int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1272 while (count < MI_CYCLES(mi)) {
1273 char buf[MAXPAT * 3 + 3], *b = buf;
1275 int l = NRAND(MAXPAT) + 1;
1276 int t = NRAND(MAXREPEAT) + 1;
1278 { /* vary number of balls */
1279 int new_balls = num_balls;
1282 if (new_balls == 2) /* Do not juggle 2 that often */
1283 change = NRAND(2 + CHANGE_BIAS / 4);
1285 change = NRAND(2 + CHANGE_BIAS);
1296 if (new_balls < MINBALLS) {
1299 if (new_balls > MAXBALLS) {
1302 if (new_balls < num_balls) {
1303 if (!program(mi, "[*]", 1)) /* lose ball */
1306 num_balls = new_balls;
1310 if (NRAND(2) && patternindex[num_balls].number) {
1311 /* Pick from PortFolio */
1313 portfolio[patternindex[num_balls].start +
1314 NRAND(patternindex[num_balls].number)].pattern,
1318 /* Invent a new pattern */
1320 for(i = 0; i < l; i++){
1322 do { /* Triangular Distribution => high values more likely */
1323 m = NRAND(num_balls + 1);
1324 n = NRAND(num_balls + 1);
1326 if (n == num_balls) {
1329 switch(NRAND(6 + POSITION_BIAS)){
1330 case 0: /* Inside throw */
1332 case 1: /* Outside throw */
1334 case 2: /* Cross throw */
1336 case 3: /* Cross catch */
1338 case 4: /* Cross throw and catch */
1340 case 5: /* Bounce */
1352 if (!program(mi, buf, t))
1357 } else { /* pattern supplied in height or 'a' notation */
1358 if (!program(mi, pattern, MI_CYCLES(mi)))
1371 if (!projectile(sp))
1381 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1385 * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1386 * trace behind when erased
1388 #define ERASE_BALL(x,y) \
1389 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1390 XFillArc(dpy, window, gc, \
1391 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1392 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1395 #define ERASE_BALL(x,y) \
1396 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1397 XFillArc(dpy, window, gc, \
1398 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1399 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1403 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1405 Display *dpy = MI_DISPLAY(mi);
1406 Window window = MI_WINDOW(mi);
1408 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1411 XSetForeground(dpy, gc, color);
1412 if ((color == MI_WHITE_PIXEL(mi)) ||
1413 ((divisions != 2) && (divisions != 4)) || sp->solid) {
1414 XFillArc(dpy, window, gc,
1415 x - BALLRADIUS, y - BALLRADIUS,
1416 2*BALLRADIUS, 2*BALLRADIUS,
1420 offset = (int) (degree_offset * 64);
1421 if (divisions == 4) { /* 90 degree divisions */
1422 XFillArc(dpy, window, gc,
1423 x - BALLRADIUS, y - BALLRADIUS,
1424 2*BALLRADIUS, 2*BALLRADIUS,
1426 XFillArc(dpy, window, gc,
1427 x - BALLRADIUS, y - BALLRADIUS,
1428 2*BALLRADIUS, 2*BALLRADIUS,
1429 (offset + 11520) % 23040, 5760);
1430 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1431 XFillArc(dpy, window, gc,
1432 x - BALLRADIUS, y - BALLRADIUS,
1433 2*BALLRADIUS, 2*BALLRADIUS,
1434 (offset + 5760) % 23040, 5760);
1435 XFillArc(dpy, window, gc,
1436 x - BALLRADIUS, y - BALLRADIUS,
1437 2*BALLRADIUS, 2*BALLRADIUS,
1438 (offset + 17280) % 23040, 5760);
1439 } else { /* 180 degree divisions */
1440 XFillArc(dpy, window, gc,
1441 x - BALLRADIUS, y - BALLRADIUS,
1442 2*BALLRADIUS, 2*BALLRADIUS,
1444 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1445 XFillArc(dpy, window, gc,
1446 x - BALLRADIUS, y - BALLRADIUS,
1447 2*BALLRADIUS, 2*BALLRADIUS,
1448 (offset + 11520) % 23040, 11520);
1454 draw_juggle(ModeInfo * mi)
1456 Display *dpy = MI_DISPLAY(mi);
1457 Window window = MI_WINDOW(mi);
1464 if (juggles == NULL)
1466 sp = &juggles[MI_SCREEN(mi)];
1467 if (sp->trace == NULL)
1470 MI_IS_DRAWN(mi) = True;
1476 # ifdef GETTIMEOFDAY_TWO_ARGS
1477 struct timezone tzp;
1478 gettimeofday(&tv, &tzp);
1483 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
1485 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
1487 if (traj->status != PREDICTOR) {
1490 if (traj->start > future) {
1491 future = traj->start;
1493 if (sp->time < traj->start) { /* early */
1495 } else if (sp->time < traj->finish) { /* working */
1496 int x = (int) CUBIC(traj->xp, sp->time);
1497 int y = (int) CUBIC(traj->yp, sp->time);
1498 unsigned long color;
1500 if (MI_NPIXELS(mi) > 2) {
1501 color = MI_PIXEL(mi, traj->color);
1503 color = MI_WHITE_PIXEL(mi);
1505 if (traj->type == Empty || traj->type == Full) {
1506 draw_arm(mi, traj->hand, &x, &y);
1508 if (traj->type == Ball || traj->type == Full) {
1510 ERASE_BALL(sp->trace[sp->traceindex].x,
1511 sp->trace[sp->traceindex].y);
1512 sp->trace[sp->traceindex].x = traj->x;
1513 sp->trace[sp->traceindex].y = traj->y;
1514 if (++sp->traceindex >= trail) {
1518 ERASE_BALL(traj->x, traj->y);
1520 draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions);
1521 traj->degree_offset = traj->degree_offset +
1522 SPIN_DEGREES * traj->spin / sp->count;
1523 if (traj->degree_offset < 0.0)
1524 traj->degree_offset += 360.0;
1525 else if (traj->degree_offset >= 360.0)
1526 traj->degree_offset -= 360.0;
1530 } else { /* expired */
1531 Trajectory *n = traj;
1533 ERASE_BALL(traj->x, traj->y);
1539 /*** FIXME-BEGIN ***/
1540 /* pattern generator would refill here when necessary */
1544 if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */
1552 #endif /* MODE_juggle */