1 /* -*- Mode: C; tab-width: 4 -*- */
4 #if !defined( lint ) && !defined( SABER )
5 static const char sccsid[] = "@(#)juggle.c 5.00 2000/11/01 xlockmore";
10 * Copyright (c) 1996 by Tim Auckland <Tim.Auckland@Procket.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 * 01-Nov-2000: Allocation checks
31 * Fix timing to run at approx same speed on all machines.
32 * Store shorter pattern and refill when required.
33 * Use -cycles and -count in a rational manner.
34 * Merge pattern selector with pattern generator.
36 * Clap when all the balls are in the air
41 Notes on Adam Chalcraft Juggling Notation (used by permission)
42 a-> Adam's notation s-> Site swap (Cambridge) notation
44 To define a map from a-notation to s-notation
45 ("site-swap"), both of which look like doubly infinite sequences of natural
46 numbers. In s-notation, there is a restriction on what is allowed, namely
47 for the sequence s_n, the associated function f(n)=n+s_n must be a
48 bijection. In a-notation, there is no restriction.
50 To go from a-notation to s-notation, you start by mapping each a_n to a
51 permutation of N, the natural numbers.
54 1 -> (10) [i.e. f(1)=0, f(0)=1]
55 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
56 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
59 Then for each n, you look at how long 0 takes to get back to 0 again and
60 you call this t_n. If a_n=0, for example, then since the identity leaves 0
61 alone, it gets back to 0 in 1 step, so t_n=1. If a_n=1, then f(0)=1. Now any
62 further a_n=0 leave 1 alone, but the next a_n>0 sends 1 back to 0. Hence t_n
63 is 2 + the number of 0's following the 1. Finally, set s_n = t_n - 1.
65 To give some examples, it helps to have a notation for cyclic sequences. By
66 (123), for example, I mean ...123123123123... . Now under the a-notation ->
67 s-notation mapping we have some familiar examples:
69 (0)->(0), (1)->(1), (2)->(2) etc.
70 (21)->(31), (31)->(51), (41)->(71) etc.
71 (10)->(20), (20)->(40), (30)->(60) etc.
72 (331)->(441), (312)->(612), (303)->(504), (321)->(531)
73 (43)->(53), (434)->(534), (433)->(633)
76 In general, the number of balls is the *average* of the s-notation, and the
77 *maximum* of the a-notation. Another theorem is that the minimum values in
78 the a-notation and the s-notation and equal, and preserved in the same
81 The usefulness of a-notation is the fact that there are no restrictions on
82 what is allowed. This makes random juggle generation much easier. It also
83 makes enumeration very easy. Another handy feature is computing changes.
84 Suppose you can do (5) and want a neat change up to (771) in s-notation
85 [Mike Day actually needed this example!]. Write them both in a-notation,
86 which gives (5) and (551). Now concatenate them (in general, there may be
87 more than one way to do this, but not in this example), to get
88 ...55555555551551551551551...
89 Now convert back to s-notation, to get
90 ...55555566771771771771771...
91 So the answer is to do two 6 throws and then go straight into (771).
92 Coming back down of course,
93 ...5515515515515515555555555...
95 ...7717717717716615555555555...
96 so the answer is to do a single 661 and then drop straight down to (5).
98 [The number of balls in the generated pattern occasionally changes. In
99 order to decrease the number of balls I had to introduce a new symbol
100 into the Adam notation, [*] which means 'lose the current ball'.]
105 #define PROGCLASS "Juggle"
106 #define HACK_INIT init_juggle
107 #define HACK_DRAW draw_juggle
108 #define juggle_opts xlockmore_opts
109 #define DEFAULTS "*delay: 10000 \n" \
113 #define SMOOTH_COLORS
114 #include "xlockmore.h" /* in xscreensaver distribution */
115 #else /* STANDALONE */
116 #include "xlock.h" /* in xlockmore distribution */
117 #endif /* STANDALONE */
121 #define DEF_PATTERN "." /* All patterns */
122 #define DEF_TRAIL "0" /* No trace */
124 #define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */
126 #define DEF_SOLID "FALSE" /* Not solid */
128 static char *pattern;
135 static XrmOptionDescRec opts[] =
137 {(char* ) "-pattern", (char *) ".juggle.pattern",
138 XrmoptionSepArg, (caddr_t) NULL},
139 {(char* ) "-trail", (char *) ".juggle.trail",
140 XrmoptionSepArg, (caddr_t) NULL},
142 {(char *) "-uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "on"},
143 {(char *) "+uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "off"},
145 {(char *) "-solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "on"},
146 {(char *) "+solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "off"}
148 static argtype vars[] =
150 {(caddr_t *) &pattern, (char *) "pattern",
151 (char *) "Pattern", (char *) DEF_PATTERN, t_String},
152 {(caddr_t *) &trail, (char *) "trail",
153 (char *) "Trail", (char *) DEF_TRAIL, t_Int},
155 {(caddr_t *) &uni, (char *) "uni",
156 (char *) "Uni", (char *) DEF_UNI, t_Bool},
158 {(caddr_t *) &solid, (char *) "solid",
159 (char *) "Solid", (char *) DEF_SOLID, t_Bool}
161 static OptionStruct desc[] =
163 {(char *) "-pattern string", (char *) "Cambridge Juggling Pattern"},
164 {(char *) "-trail num", (char *) "Trace Juggling Patterns"},
166 {(char *) "-/+uni", (char *) "Unicycle"},
168 {(char *) "-/+solid", (char *) "solid color (else its a 4 panel look (half white))"}
171 ModeSpecOpt juggle_opts =
172 {sizeof opts / sizeof opts[0], opts,
173 sizeof vars / sizeof vars[0], vars, desc};
176 ModStruct juggle_description = {
177 "juggle", "init_juggle", "draw_juggle", "release_juggle",
178 "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
179 10000, 150, 30, 1, 64, 1.0, "",
180 "Shows a Juggler, juggling", 0, NULL
186 #include <X11/unix_time.h>
190 #include <sys/time.h>
192 #if HAVE_SYS_SELECT_H
193 #include <sys/select.h>
198 #define ARMLENGTH ((int) (40.0 * sp->scale))
199 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
200 #define POSE ((int) (10.0 * sp->scale))
201 #define SX ((int) (25.0 * sp->scale))
202 #define SZ ((int) (25.0 * sp->scale))
203 #define SY ((int) (25.0 * sp->scale))
204 #define HIPY ((int) (85.0 * sp->scale))
205 #define RHIPX ((int) (-15.0 * sp->scale))
206 #define LHIPX ((int) (15.0 * sp->scale))
207 #define RFX ((int) (-25.0 * sp->scale))
208 #define LFX ((int) (25.0 * sp->scale))
209 #define FY ((int) (155.0 * sp->scale))
210 #define WSTY ((int) (65.0 * sp->scale))
211 #define NEY ((int) (15.0 * sp->scale))
212 #define HED ((int) (35.0 * sp->scale))
213 #define BALLRADIUS ARMWIDTH
216 #define TRACE_LENGTH 50
217 #define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */
222 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
225 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
227 #define THROW_CATCH_INTERVAL (sp->count)
228 #define THROW_NULL_INTERVAL (sp->count * 0.5)
229 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
230 #define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */
235 typedef enum {HEIGHT, ADAM} Notation;
236 typedef enum {Empty, Full, Ball} Throwable;
237 typedef enum {LEFT, RIGHT} Hand;
238 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
239 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
240 PREDICTOR} TrajectoryStatus;
242 typedef struct trajectory *TrajectoryPtr;
244 typedef struct {double a, b, c, d; } Spline;
246 typedef struct trajectory {
247 TrajectoryPtr prev, next; /* for building list */
248 TrajectoryStatus status;
262 double degree_offset;
263 TrajectoryPtr balllink;
264 TrajectoryPtr handlink;
268 double dx; /* initial velocity */
275 int x, y; /* current position */
290 XPoint figure_path[FIGURE1];
291 XSegment figure_segs[FIGURE2];
296 time_t begintime; /* seconds */
297 int time; /* millisecond timer */
301 static jugglestruct *juggles = (jugglestruct *) NULL;
316 static PatternIndex* patternindex = (PatternIndex *) NULL;
318 /* List of popular patterns, in any order */
319 static patternstruct portfolio[] = {
320 {(char *) "[+2 1]", (char *) "+3 1, Typical 2 ball juggler"},
321 {(char *) "[2 0]", (char *) "4 0, 2 balls 1 hand"},
322 {(char *) "[2 0 1]", (char *) "5 0 1"},
323 {(char *) "[+2 0 +2 0 0]", (char *) "+5 0 +5 0 0"},
324 {(char *) "[3]", (char *) "3, cascade"},
325 {(char *) "[+3]", (char *) "+3, reverse cascade"},
326 {(char *) "[=3]", (char *) "=3, cascade under arm"},
327 {(char *) "[&3]", (char *) "&3, cascade catching under arm"},
328 {(char *) "[_3]", (char *) "_3, bouncing cascade"},
329 {(char *) "[+3 x3 =3]", (char *) "+3 x3 =3, Mill's mess"},
330 {(char *) "[3 2 1]", (char *) "5 3 1"},
331 {(char *) "[3 3 1]", (char *) "4 4 1"},
332 {(char *) "[3 1 2]", (char *) "6 1 2, See-saw"},
333 {(char *) "[=3 3 1 2]", (char *) "=4 5 1 2"},
334 {(char *) "[=3 2 2 3 1 2]", (char *) "=6 2 2 5 1 2, =4 5 1 2 stretched"},
335 {(char *) "[+3 3 1 3]", (char *) "+4 4 1 3, anemic shower box"},
336 {(char *) "[3 3 1]", (char *) "4 4 1"},
337 {(char *) "[+3 2 3]", (char *) "+4 2 3"},
338 {(char *) "[+3 1]", (char *) "+5 1, 3 shower"},
339 {(char *) "[_3 1]", (char *) "_5 1, bouncing 3 shower"},
340 {(char *) "[3 0 3 0 3]", (char *) "5 0 5 0 5, shake 3 out of 5"},
341 {(char *) "[3 3 3 0 0]", (char *) "5 5 5 0 0, flash 3 out of 5"},
342 {(char *) "[3 3 0]", (char *) "4 5 0, complete waste of a 5 ball juggler"},
343 {(char *) "[3 3 3 0 0 0 0]", (char *) "7 7 7 0 0 0 0, 3 flash"},
344 {(char *) "[+3 0 +3 0 +3 0 0]", (char *) "+7 0 +7 0 +7 0 0"},
345 {(char *) "[4]", (char *) "4, 4 cascade"},
346 {(char *) "[+4 3]", (char *) "+5 3, 4 ball half shower"},
347 {(char *) "[4 4 2]", (char *) "5 5 2"},
348 {(char *) "[+4 4 4 +4]", (char *) "+4 4 4 +4, 4 columns"},
349 {(char *) "[4 3 +4]", (char *) "5 3 +4"},
350 {(char *) "[+4 1]", (char *) "+7 1, 4 shower"},
351 {(char *) "[4 4 4 4 0]", (char *) "5 5 5 5 0, learning 5"},
352 {(char *) "[5]", (char *) "5, 5 cascade"},
353 {(char *) "[_5 _5 _5 _5 _5 5 5 5 5 5]", (char *) "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
354 {(char *) "[+5 x5 =5]", (char *) "+5 x5 =5, Mill's mess for 5"},
355 {(char *) "[6]", (char *) "6, 6 cascade"},
356 {(char *) "[7]", (char *) "7, 7 cascade"},
357 {(char *) "[_7]", (char *) "_7, bouncing 7 cascade"},
360 /* Private Functions */
362 /* list management */
364 /* t receives trajectory to be created. ot must point to an existing
365 trajectory or be identical to t to start a new list. */
366 #define INSERT_AFTER_TOP(t, ot) \
367 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
368 {free_juggle(sp); return;} \
369 (t)->next = (ot)->next; \
372 (t)->next->prev = (t)
373 #define INSERT_AFTER(t, ot) \
374 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
375 {free_juggle(sp); return False;} \
376 (t)->next = (ot)->next; \
379 (t)->next->prev = (t)
382 /* t must point to an existing trajectory. t must not be an
383 expression ending ->next or ->prev */
385 (t)->next->prev = (t)->prev; \
386 (t)->prev->next = (t)->next; \
387 (void) free((void *) t)
390 free_pattern(jugglestruct *sp) {
391 if (sp->head != NULL) {
392 while (sp->head->next != sp->head) {
393 Trajectory *t = sp->head->next;
395 REMOVE(t); /* don't eliminate t */
397 (void) free((void *) sp->head);
398 sp->head = (Trajectory *) NULL;
403 free_juggle(jugglestruct *sp)
405 if (sp->trace != NULL) {
406 (void) free((void *) sp->trace);
407 sp->trace = (XPoint *) NULL;
413 add_throw(jugglestruct *sp, char type, int h, Notation n)
417 INSERT_AFTER(t, sp->head->prev);
429 /* add a Thratch to the performance */
431 program(ModeInfo *mi, const char *patn, int repeat)
433 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
439 if (MI_IS_VERBOSE(mi)) {
440 (void) fprintf(stderr, "%s x %d\n", patn, repeat);
443 for(i=0; i < repeat; i++) {
448 for(p=patn; *p; p++) {
449 if (*p >= '0' && *p <='9') {
451 h = 10*h + (*p - '0');
453 Notation nn = notation;
455 case '[': /* begin Adam notation */
458 case '-': /* Inside throw */
459 case '+': /* Outside throw */
460 case '=': /* Cross throw */
461 case '&': /* Cross catch */
462 case 'x': /* Cross throw and catch */
463 case '_': /* Bounce */
466 case '*': /* Lose ball */
470 case ']': /* end Adam notation */
475 if (!add_throw(sp, type, h, notation))
484 (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
490 if (!add_throw(sp, type, h, notation))
505 4 4 1 3 12 2 4 1 4 4 13 0 3 1
508 #define BOUNCEOVER 10
511 adam(jugglestruct *sp)
514 for(t = sp->head->next; t != sp->head; t = t->next) {
515 if (t->status == ATCH) {
518 for(p = t->next; a > 0 && p != sp->head; p = p->next) {
519 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
524 if(t->height > BOUNCEOVER && t->posn == ' '){
525 t->posn = '_'; /* high defaults can be bounced */
529 (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
535 /* Split Thratch notation into explicit throws and catches.
536 Usually Catch follows Throw in same hand, but take care of special
539 /* ..n1.. -> .. LTn RT1 LC RC .. */
540 /* ..nm.. -> .. LTn LC RTm RC .. */
543 part(jugglestruct *sp)
545 Trajectory *t, *nt, *p;
546 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
548 for (t = sp->head->next; t != sp->head; t = t->next) {
549 if (t->status > THRATCH) {
551 } else if (t->status == THRATCH) {
554 /* plausibility check */
555 if (t->height <= 2 && t->posn == '_') {
556 t->posn = ' '; /* no short bounces */
558 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
559 t->posn = ' '; /* 1's need close catches */
564 case ' ': /* fall through */
565 case '-': posn = '-'; t->posn = '+'; break;
566 case '+': posn = '+'; t->posn = '-'; break;
567 case '=': posn = '='; t->posn = '+'; break;
568 case '&': posn = '+'; t->posn = '='; break;
569 case 'x': posn = '='; t->posn = '='; break;
570 case '_': posn = '_'; t->posn = '-'; break;
571 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
573 hand = (Hand) ((hand + 1) % 2);
578 if (t->height == 1) {
579 p = p->prev; /* early throw */
585 nt->height = t->height;
593 /* Connnect up throws and catches to figure out which ball goes where.
594 Do the same with the juggler's hands. */
599 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
602 for (t = sp->head->next; t != sp->head; t = t->next) {
603 if (t->status == ACTION) {
605 (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
608 (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
611 if (t->action == THROW) {
612 if (t->type == Empty) {
613 if (MI_NPIXELS(mi) > 2) {
614 t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
616 t->spin = NRAND(5) - 2;
617 t->degree_offset = NRAND(360);
618 t->divisions = 2 * ((LRAND() & 1) + 1);
621 /* search forward for next hand catch */
622 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
623 if (p->action == CATCH) {
624 if (t->handlink == NULL && p->hand == t->hand) {
625 t->handlink = p; /* next catch in this hand */
633 /* search forward for next ball catch */
634 for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
635 if (p->action == CATCH) {
636 if (t->balllink == NULL && --h < 1) { /* caught */
638 if (p->type == Full) {
642 t->balllink = p; /* complete trajectory */
644 p->color = t->color; /* accept catch */
646 p->degree_offset = t->degree_offset;
647 p->divisions = t->divisions;
652 t->type = Empty; /* thrown */
653 } else if (t->action == CATCH) {
654 /* search forward for next throw from this hand */
655 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
656 if (p->action == THROW && p->hand == t->hand) {
657 p->type = t->type; /* pass ball */
658 p->color = t->color; /* pass color */
659 p->spin = NRAND(5) - 2;
660 p->degree_offset = NRAND(360);
661 p->divisions = 2 * ((LRAND() & 1) + 1);
666 t->status = LINKEDACTION;
671 /* Convert hand position symbols into actual time/space coordinates */
673 positions(jugglestruct *sp)
677 for (t = sp->head->next; t != sp->head; t = t->next) {
678 if (t->status == PTHRATCH) {
680 } else if (t->status == LINKEDACTION) {
684 if (t->action == CATCH) {
685 if (t->type == Empty) {
686 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
688 now += THROW_CATCH_INTERVAL; /* successful catch */
691 now += (int) CATCH_THROW_INTERVAL; /* throws are very short */
698 case '-': xo = SX - POSE; break;
700 case '+': xo = SX + POSE; break;
701 case '=': xo = - SX - POSE; yo += 2 * POSE; break;
702 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
704 t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
707 t->status = PTHRATCH;
713 /* Private physics functions */
716 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
725 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
726 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
731 s.c = (3*a*t0 - 2*b)*t0 + c;
732 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
737 makeSplinePair(Spline *s1, Spline *s2,
738 int x0, double dx0, int t0,
740 int x2, double dx2, int t2)
743 double t21, t10, t20, dx1;
749 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
750 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
752 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
753 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
757 /* Turn abstract timings into physically appropriate ball trajectories. */
759 projectile(jugglestruct *sp)
762 for (t = sp->head->next; t != sp->head; t = t->next) {
763 if (t->status != PTHRATCH) {
766 if (t->action == THROW) {
767 if (t->balllink != NULL) {
768 if (t->posn == '_') { /* Bounce once */
769 double tc, y0, yf, yc, tb, e, i;
771 tc = t->balllink->start - t->start;
776 e = 1; /* permissible error in yc */
780 yt = height at catch time after one bounce
781 one or three roots according to timing
782 find one by interval bisection
785 for(i = tc / 2; i > 0; i/=2){
788 (void) fprintf(stderr, "div by zero!\n");
791 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
793 yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
796 }else if(yt < yc - e){
806 t->dx = (t->balllink->x - t->x) / tc;
808 /* ball follows parabola down */
809 INSERT_AFTER(n, t->prev);
811 n->finish = (int) (t->start + tb);
815 n->degree_offset = t->degree_offset;
816 n->divisions = t->divisions;
817 n->status = PREDICTOR;
819 t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
821 /* Represent parabola as a degenerate spline -
822 linear in x, quadratic in y */
826 n->xp.d = -t->dx*t0 + t->x;
829 n->yp.c = -sp->Gr*t0 + t->dy;
830 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
833 /* ball follows parabola up */
834 INSERT_AFTER(n, t->prev);
835 n->start = (int) (t0 + tb);
836 n->finish = (int) (t0 + tc);
840 n->degree_offset = t->degree_offset;
841 n->divisions = t->divisions;
842 n->status = PREDICTOR;
847 n->xp.d = -t->dx*t0 + t->x;
849 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
851 /* Represent parabola as a degenerate spline -
852 linear in x, quadratic in y */
855 n->yp.c = -sp->Gr*t0 - COR*dy;
856 n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
859 t->status = BPREDICTOR;
864 /* ball follows parabola */
865 INSERT_AFTER(n, t->prev);
867 n->finish = t->balllink->start;
871 n->degree_offset = t->degree_offset;
872 n->divisions = t->divisions;
873 n->status = PREDICTOR;
876 dt = t->balllink->start - t->start;
877 t->dx = (t->balllink->x - t->x) / dt;
878 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
880 /* Represent parabola as a degenerate spline -
881 linear in x, quadratic in y */
885 n->xp.d = -t->dx*t0 + t->x;
888 n->yp.c = -sp->Gr*t0 + t->dy;
889 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
892 t->status = BPREDICTOR;
894 } else { /* Zero Throw */
895 t->status = BPREDICTOR;
902 /* Turn abstract hand motions into cubic splines. */
904 hands(jugglestruct *sp)
906 Trajectory *t, *u, *v;
907 for (t = sp->head->next; t != sp->head; t = t->next) {
908 /* no throw => no velocity */
909 if (t->status != BPREDICTOR) {
914 if (u == NULL) { /* no next catch */
918 if (v == NULL) { /* no next throw */
922 /* double spline takes hand from throw, thru catch, to
925 t->finish = u->start;
926 t->status = PREDICTOR;
928 u->finish = v->start;
929 u->status = PREDICTOR;
931 (void) makeSplinePair(&t->xp, &u->xp,
932 t->x, t->dx, t->start,
934 v->x, v->dx, v->start);
935 (void) makeSplinePair(&t->yp, &u->yp,
936 t->y, t->dy, t->start,
938 v->y, v->dy, v->start);
940 t->status = PREDICTOR;
944 /* Given target x, y find_elbow puts hand at target if possible,
945 * otherwise makes hand point to the target */
947 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
951 h2 = x*x + y*y + z*z;
952 if (h2 > 4*ARMLENGTH*ARMLENGTH) {
953 t = ARMLENGTH/sqrt(h2);
954 e->x = (short) (t*x);
955 e->y = (short) (t*y);
959 r = sqrt((double)(x*x + z*z));
960 t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
961 e->x = (short) (x*(1 - y*t/r)/2);
962 e->y = (short) ((y + r*t)/2);
968 /* NOTE: returned x, y adjusted for arm reach */
970 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
972 Display *dpy = MI_DISPLAY(mi);
973 Window win = MI_WINDOW(mi);
975 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
977 int sig = (side == LEFT) ? 1 : -1;
979 XSetLineAttributes(dpy, gc,
980 ARMWIDTH, LineSolid, CapRound, JoinRound);
981 if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
983 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
984 find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
985 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
986 *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
987 *y = sp->arm[side][0].y = sp->cy + SY + h.y;
988 sp->arm[side][1].x = sp->cx + sig*SX + e.x;
989 sp->arm[side][1].y = sp->cy + SY + e.y;
991 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
992 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
993 XSetLineAttributes(dpy, gc,
994 1, LineSolid, CapNotLast, JoinRound);
998 draw_figure(ModeInfo * mi)
1000 Display *dpy = MI_DISPLAY(mi);
1001 Window win = MI_WINDOW(mi);
1003 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1005 XSetLineAttributes(dpy, gc,
1006 ARMWIDTH, LineSolid, CapRound, JoinRound);
1007 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1008 XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1009 XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1010 XDrawArc(dpy, win, gc,
1011 sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1012 XSetLineAttributes(dpy, gc,
1013 1, LineSolid, CapNotLast, JoinRound);
1017 /* dumps a human-readable rendition of the current state of the juggle
1018 pipeline to stderr for debugging */
1021 dump(jugglestruct *sp)
1025 for (t = sp->head->next; t != sp->head; t = t->next) {
1026 switch (t->status) {
1028 (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1031 (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1033 t->hand ? 'R' : 'L',
1034 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1038 (void) fprintf(stderr, "L %c%c%c%d %d\n",
1041 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1042 t->height, t->color);
1045 (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1047 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1048 t->height, t->type, t->color,
1049 t->start, t->finish);
1052 (void) fprintf(stderr, "P %c %2d %6d %6d %g\n",
1053 t->type == Ball?'b':t->type == Empty?'e':'f',
1055 t->start, t->finish, t->yp.c);
1058 (void) fprintf(stderr, "status %d not implemented\n", t->status);
1065 static int get_num_balls(const char *j)
1070 for (p = j; *p; p++) {
1071 if (*p >= '0' && *p <='9') { /* digit */
1072 h = 10*h + (*p - '0');
1087 static int compare_num_balls(const void *p1, const void *p2)
1089 int i = get_num_balls(((patternstruct*)p1)->pattern);
1090 int j = get_num_balls(((patternstruct*)p2)->pattern);
1104 /* Public functions */
1107 release_juggle(ModeInfo * mi)
1109 if (juggles != NULL) {
1112 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1113 free_juggle(&juggles[screen]);
1114 (void) free((void *) juggles);
1115 juggles = (jugglestruct *) NULL;
1117 if (patternindex != NULL) {
1118 (void) free((void *) patternindex);
1119 patternindex = (PatternIndex *) NULL;
1124 init_juggle(ModeInfo * mi)
1128 XPoint figure1[FIGURE1];
1129 XSegment figure2[FIGURE2];
1130 if (pattern != NULL && *pattern == '.') {
1133 if (pattern == NULL && patternindex == NULL) {
1134 /* pattern list needs indexing */
1136 int nelements = sizeof(portfolio)/sizeof(patternstruct);
1140 /* sort according to number of balls */
1141 qsort((void*)portfolio, nelements,
1142 sizeof(patternstruct), compare_num_balls);
1144 /* last pattern has most balls */
1145 maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1146 /* allocate index */
1147 if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1148 sizeof (PatternIndex))) == NULL) {
1152 /* run through sorted list, indexing start of each group
1153 and number in group */
1155 for (i = 0; i < nelements; i++) {
1156 int b = get_num_balls(portfolio[i].pattern);
1158 if (MI_IS_VERBOSE(mi)) {
1159 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1160 numpat, maxballs, (numpat == 1) ? "" : "s");
1162 patternindex[maxballs].number = numpat;
1165 patternindex[maxballs].start = i;
1170 if (MI_IS_VERBOSE(mi)) {
1171 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1172 numpat, maxballs, (numpat == 1) ? "" : "s");
1174 patternindex[maxballs].number = numpat;
1177 if (juggles == NULL) { /* allocate jugglestruct */
1178 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1179 sizeof (jugglestruct))) == NULL) {
1184 sp = &juggles[MI_SCREEN(mi)];
1188 if (MI_IS_FULLRANDOM(mi)) {
1189 sp->solid = (Bool) (LRAND() & 1);
1191 sp->uni = (Bool) (LRAND() & 1);
1200 sp->width = MI_WIDTH(mi);
1201 sp->height = MI_HEIGHT(mi);
1202 sp->count = ABS(MI_COUNT(mi));
1205 sp->scale = sp->height / 480.0;
1206 /* vary x a little so the juggler does not burn the screen */
1207 sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1208 sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1209 /* "7" hits top of screen */
1210 sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1212 figure1[0].x = LHIPX, figure1[0].y = HIPY;
1213 figure1[1].x = 0, figure1[1].y = WSTY;
1214 figure1[2].x = SX, figure1[2].y = SY;
1215 figure1[3].x = -SX, figure1[3].y = SY;
1216 figure1[4].x = 0, figure1[4].y = WSTY;
1217 figure1[5].x = RHIPX, figure1[5].y = HIPY;
1218 figure1[6].x = LHIPX, figure1[6].y = HIPY;
1219 figure2[0].x1 = 0, figure2[0].y1 = SY,
1220 figure2[0].x2 = 0, figure2[0].y2 = NEY;
1221 figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1222 figure2[1].x2 = LFX, figure2[1].y2 = FY;
1223 figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1224 figure2[2].x2 = RFX, figure2[2].y2 = FY;
1227 for (i = 0; i < FIGURE1; i++) {
1228 sp->figure_path[i].x = figure1[i].x + sp->cx;
1229 sp->figure_path[i].y = figure1[i].y + sp->cy;
1232 for (i = 0; i < FIGURE2; i++) {
1233 sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1234 sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1235 sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1236 sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1239 sp->arm[LEFT][2].x = sp->cx + SX;
1240 sp->arm[LEFT][2].y = sp->cy + SY;
1241 sp->arm[RIGHT][2].x = sp->cx - SX;
1242 sp->arm[RIGHT][2].y = sp->cy + SY;
1244 if (sp->trace == NULL) {
1245 if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1251 /* Clear the background. */
1256 /* record start time */
1257 sp->begintime = time(NULL);
1261 /* create circular list */
1262 INSERT_AFTER_TOP(sp->head, sp->head);
1264 /* generate pattern */
1265 if (pattern == NULL) {
1268 #define MAXREPEAT 30
1269 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1270 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1273 int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1274 while (count < MI_CYCLES(mi)) {
1275 char buf[MAXPAT * 3 + 3], *b = buf;
1277 int l = NRAND(MAXPAT) + 1;
1278 int t = NRAND(MAXREPEAT) + 1;
1280 { /* vary number of balls */
1281 int new_balls = num_balls;
1284 if (new_balls == 2) /* Do not juggle 2 that often */
1285 change = NRAND(2 + CHANGE_BIAS / 4);
1287 change = NRAND(2 + CHANGE_BIAS);
1298 if (new_balls < MINBALLS) {
1301 if (new_balls > MAXBALLS) {
1304 if (new_balls < num_balls) {
1305 if (!program(mi, "[*]", 1)) /* lose ball */
1308 num_balls = new_balls;
1312 if (NRAND(2) && patternindex[num_balls].number) {
1313 /* Pick from PortFolio */
1315 portfolio[patternindex[num_balls].start +
1316 NRAND(patternindex[num_balls].number)].pattern,
1320 /* Invent a new pattern */
1322 for(i = 0; i < l; i++){
1324 do { /* Triangular Distribution => high values more likely */
1325 m = NRAND(num_balls + 1);
1326 n = NRAND(num_balls + 1);
1328 if (n == num_balls) {
1331 switch(NRAND(6 + POSITION_BIAS)){
1332 case 0: /* Inside throw */
1334 case 1: /* Outside throw */
1336 case 2: /* Cross throw */
1338 case 3: /* Cross catch */
1340 case 4: /* Cross throw and catch */
1342 case 5: /* Bounce */
1354 if (!program(mi, buf, t))
1359 } else { /* pattern supplied in height or 'a' notation */
1360 if (!program(mi, pattern, MI_CYCLES(mi)))
1373 if (!projectile(sp))
1383 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1387 * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1388 * trace behind when erased
1390 #define ERASE_BALL(x,y) \
1391 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1392 XFillArc(dpy, window, gc, \
1393 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1394 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1397 #define ERASE_BALL(x,y) \
1398 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1399 XFillArc(dpy, window, gc, \
1400 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1401 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1405 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1407 Display *dpy = MI_DISPLAY(mi);
1408 Window window = MI_WINDOW(mi);
1410 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1413 XSetForeground(dpy, gc, color);
1414 if ((color == MI_WHITE_PIXEL(mi)) ||
1415 ((divisions != 2) && (divisions != 4)) || sp->solid) {
1416 XFillArc(dpy, window, gc,
1417 x - BALLRADIUS, y - BALLRADIUS,
1418 2*BALLRADIUS, 2*BALLRADIUS,
1422 offset = (int) (degree_offset * 64);
1423 if (divisions == 4) { /* 90 degree divisions */
1424 XFillArc(dpy, window, gc,
1425 x - BALLRADIUS, y - BALLRADIUS,
1426 2*BALLRADIUS, 2*BALLRADIUS,
1428 XFillArc(dpy, window, gc,
1429 x - BALLRADIUS, y - BALLRADIUS,
1430 2*BALLRADIUS, 2*BALLRADIUS,
1431 (offset + 11520) % 23040, 5760);
1432 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1433 XFillArc(dpy, window, gc,
1434 x - BALLRADIUS, y - BALLRADIUS,
1435 2*BALLRADIUS, 2*BALLRADIUS,
1436 (offset + 5760) % 23040, 5760);
1437 XFillArc(dpy, window, gc,
1438 x - BALLRADIUS, y - BALLRADIUS,
1439 2*BALLRADIUS, 2*BALLRADIUS,
1440 (offset + 17280) % 23040, 5760);
1441 } else { /* 180 degree divisions */
1442 XFillArc(dpy, window, gc,
1443 x - BALLRADIUS, y - BALLRADIUS,
1444 2*BALLRADIUS, 2*BALLRADIUS,
1446 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1447 XFillArc(dpy, window, gc,
1448 x - BALLRADIUS, y - BALLRADIUS,
1449 2*BALLRADIUS, 2*BALLRADIUS,
1450 (offset + 11520) % 23040, 11520);
1456 draw_juggle(ModeInfo * mi)
1458 Display *dpy = MI_DISPLAY(mi);
1459 Window window = MI_WINDOW(mi);
1466 if (juggles == NULL)
1468 sp = &juggles[MI_SCREEN(mi)];
1469 if (sp->trace == NULL)
1472 MI_IS_DRAWN(mi) = True;
1478 (void)gettimeofday(&tv, NULL);
1479 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
1481 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
1483 if (traj->status != PREDICTOR) {
1486 if (traj->start > future) {
1487 future = traj->start;
1489 if (sp->time < traj->start) { /* early */
1491 } else if (sp->time < traj->finish) { /* working */
1492 int x = (int) CUBIC(traj->xp, sp->time);
1493 int y = (int) CUBIC(traj->yp, sp->time);
1494 unsigned long color;
1496 if (MI_NPIXELS(mi) > 2) {
1497 color = MI_PIXEL(mi, traj->color);
1499 color = MI_WHITE_PIXEL(mi);
1501 if (traj->type == Empty || traj->type == Full) {
1502 draw_arm(mi, traj->hand, &x, &y);
1504 if (traj->type == Ball || traj->type == Full) {
1506 ERASE_BALL(sp->trace[sp->traceindex].x,
1507 sp->trace[sp->traceindex].y);
1508 sp->trace[sp->traceindex].x = traj->x;
1509 sp->trace[sp->traceindex].y = traj->y;
1510 if (++sp->traceindex >= trail) {
1514 ERASE_BALL(traj->x, traj->y);
1516 draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions);
1517 traj->degree_offset = traj->degree_offset +
1518 SPIN_DEGREES * traj->spin / sp->count;
1519 if (traj->degree_offset < 0.0)
1520 traj->degree_offset += 360.0;
1521 else if (traj->degree_offset >= 360.0)
1522 traj->degree_offset -= 360.0;
1526 } else { /* expired */
1527 Trajectory *n = traj;
1529 ERASE_BALL(traj->x, traj->y);
1535 /*** FIXME-BEGIN ***/
1536 /* pattern generator would refill here when necessary */
1540 if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */
1548 #endif /* MODE_juggle */