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'.]
106 #define PROGCLASS "Juggle"
107 #define HACK_INIT init_juggle
108 #define HACK_DRAW draw_juggle
109 #define juggle_opts xlockmore_opts
110 #define DEFAULTS "*delay: 10000 \n" \
114 #define SMOOTH_COLORS
115 #include "xlockmore.h" /* in xscreensaver distribution */
116 #else /* STANDALONE */
117 #include "xlock.h" /* in xlockmore distribution */
118 #endif /* STANDALONE */
122 #define DEF_PATTERN "." /* All patterns */
123 #define DEF_TRAIL "0" /* No trace */
125 #define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */
127 #define DEF_SOLID "FALSE" /* Not solid */
129 static char *pattern;
136 static XrmOptionDescRec opts[] =
138 {(char* ) "-pattern", (char *) ".juggle.pattern",
139 XrmoptionSepArg, (caddr_t) NULL},
140 {(char* ) "-trail", (char *) ".juggle.trail",
141 XrmoptionSepArg, (caddr_t) NULL},
143 {(char *) "-uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "on"},
144 {(char *) "+uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "off"},
146 {(char *) "-solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "on"},
147 {(char *) "+solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "off"}
149 static argtype vars[] =
151 {(caddr_t *) &pattern, (char *) "pattern",
152 (char *) "Pattern", (char *) DEF_PATTERN, t_String},
153 {(caddr_t *) &trail, (char *) "trail",
154 (char *) "Trail", (char *) DEF_TRAIL, t_Int},
156 {(caddr_t *) &uni, (char *) "uni",
157 (char *) "Uni", (char *) DEF_UNI, t_Bool},
159 {(caddr_t *) &solid, (char *) "solid",
160 (char *) "Solid", (char *) DEF_SOLID, t_Bool}
162 static OptionStruct desc[] =
164 {(char *) "-pattern string", (char *) "Cambridge Juggling Pattern"},
165 {(char *) "-trail num", (char *) "Trace Juggling Patterns"},
167 {(char *) "-/+uni", (char *) "Unicycle"},
169 {(char *) "-/+solid", (char *) "solid color (else its a 4 panel look (half white))"}
172 ModeSpecOpt juggle_opts =
173 {sizeof opts / sizeof opts[0], opts,
174 sizeof vars / sizeof vars[0], vars, desc};
177 ModStruct juggle_description = {
178 "juggle", "init_juggle", "draw_juggle", "release_juggle",
179 "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
180 10000, 150, 30, 1, 64, 1.0, "",
181 "Shows a Juggler, juggling", 0, NULL
187 #include <X11/unix_time.h>
191 #include <sys/time.h>
193 #if HAVE_SYS_SELECT_H
194 #include <sys/select.h>
199 #define ARMLENGTH ((int) (40.0 * sp->scale))
200 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
201 #define POSE ((int) (10.0 * sp->scale))
202 #define SX ((int) (25.0 * sp->scale))
203 #define SZ ((int) (25.0 * sp->scale))
204 #define SY ((int) (25.0 * sp->scale))
205 #define HIPY ((int) (85.0 * sp->scale))
206 #define RHIPX ((int) (-15.0 * sp->scale))
207 #define LHIPX ((int) (15.0 * sp->scale))
208 #define RFX ((int) (-25.0 * sp->scale))
209 #define LFX ((int) (25.0 * sp->scale))
210 #define FY ((int) (155.0 * sp->scale))
211 #define WSTY ((int) (65.0 * sp->scale))
212 #define NEY ((int) (15.0 * sp->scale))
213 #define HED ((int) (35.0 * sp->scale))
214 #define BALLRADIUS ARMWIDTH
217 #define TRACE_LENGTH 50
218 #define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */
223 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
226 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
228 #define THROW_CATCH_INTERVAL (sp->count)
229 #define THROW_NULL_INTERVAL (sp->count * 0.5)
230 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
231 #define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */
236 typedef enum {HEIGHT, ADAM} Notation;
237 typedef enum {Empty, Full, Ball} Throwable;
238 typedef enum {LEFT, RIGHT} Hand;
239 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
240 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
241 PREDICTOR} TrajectoryStatus;
243 typedef struct trajectory *TrajectoryPtr;
245 typedef struct {double a, b, c, d; } Spline;
247 typedef struct trajectory {
248 TrajectoryPtr prev, next; /* for building list */
249 TrajectoryStatus status;
263 double degree_offset;
264 TrajectoryPtr balllink;
265 TrajectoryPtr handlink;
269 double dx; /* initial velocity */
276 int x, y; /* current position */
291 XPoint figure_path[FIGURE1];
292 XSegment figure_segs[FIGURE2];
297 time_t begintime; /* seconds */
298 int time; /* millisecond timer */
302 static jugglestruct *juggles = (jugglestruct *) NULL;
317 static PatternIndex* patternindex = (PatternIndex *) NULL;
319 /* List of popular patterns, in any order */
320 static patternstruct portfolio[] = {
321 {(char *) "[+2 1]", (char *) "+3 1, Typical 2 ball juggler"},
322 {(char *) "[2 0]", (char *) "4 0, 2 balls 1 hand"},
323 {(char *) "[2 0 1]", (char *) "5 0 1"},
324 {(char *) "[+2 0 +2 0 0]", (char *) "+5 0 +5 0 0"},
325 {(char *) "[3]", (char *) "3, cascade"},
326 {(char *) "[+3]", (char *) "+3, reverse cascade"},
327 {(char *) "[=3]", (char *) "=3, cascade under arm"},
328 {(char *) "[&3]", (char *) "&3, cascade catching under arm"},
329 {(char *) "[_3]", (char *) "_3, bouncing cascade"},
330 {(char *) "[+3 x3 =3]", (char *) "+3 x3 =3, Mill's mess"},
331 {(char *) "[3 2 1]", (char *) "5 3 1"},
332 {(char *) "[3 3 1]", (char *) "4 4 1"},
333 {(char *) "[3 1 2]", (char *) "6 1 2, See-saw"},
334 {(char *) "[=3 3 1 2]", (char *) "=4 5 1 2"},
335 {(char *) "[=3 2 2 3 1 2]", (char *) "=6 2 2 5 1 2, =4 5 1 2 stretched"},
336 {(char *) "[+3 3 1 3]", (char *) "+4 4 1 3, anemic shower box"},
337 {(char *) "[3 3 1]", (char *) "4 4 1"},
338 {(char *) "[+3 2 3]", (char *) "+4 2 3"},
339 {(char *) "[+3 1]", (char *) "+5 1, 3 shower"},
340 {(char *) "[_3 1]", (char *) "_5 1, bouncing 3 shower"},
341 {(char *) "[3 0 3 0 3]", (char *) "5 0 5 0 5, shake 3 out of 5"},
342 {(char *) "[3 3 3 0 0]", (char *) "5 5 5 0 0, flash 3 out of 5"},
343 {(char *) "[3 3 0]", (char *) "4 5 0, complete waste of a 5 ball juggler"},
344 {(char *) "[3 3 3 0 0 0 0]", (char *) "7 7 7 0 0 0 0, 3 flash"},
345 {(char *) "[+3 0 +3 0 +3 0 0]", (char *) "+7 0 +7 0 +7 0 0"},
346 {(char *) "[4]", (char *) "4, 4 cascade"},
347 {(char *) "[+4 3]", (char *) "+5 3, 4 ball half shower"},
348 {(char *) "[4 4 2]", (char *) "5 5 2"},
349 {(char *) "[+4 4 4 +4]", (char *) "+4 4 4 +4, 4 columns"},
350 {(char *) "[4 3 +4]", (char *) "5 3 +4"},
351 {(char *) "[+4 1]", (char *) "+7 1, 4 shower"},
352 {(char *) "[4 4 4 4 0]", (char *) "5 5 5 5 0, learning 5"},
353 {(char *) "[5]", (char *) "5, 5 cascade"},
354 {(char *) "[_5 _5 _5 _5 _5 5 5 5 5 5]", (char *) "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
355 {(char *) "[+5 x5 =5]", (char *) "+5 x5 =5, Mill's mess for 5"},
356 {(char *) "[6]", (char *) "6, 6 cascade"},
357 {(char *) "[7]", (char *) "7, 7 cascade"},
358 {(char *) "[_7]", (char *) "_7, bouncing 7 cascade"},
361 /* Private Functions */
363 /* list management */
365 /* t receives trajectory to be created. ot must point to an existing
366 trajectory or be identical to t to start a new list. */
367 #define INSERT_AFTER_TOP(t, ot) \
368 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
369 {free_juggle(sp); return;} \
370 (t)->next = (ot)->next; \
373 (t)->next->prev = (t)
374 #define INSERT_AFTER(t, ot) \
375 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
376 {free_juggle(sp); return False;} \
377 (t)->next = (ot)->next; \
380 (t)->next->prev = (t)
383 /* t must point to an existing trajectory. t must not be an
384 expression ending ->next or ->prev */
386 (t)->next->prev = (t)->prev; \
387 (t)->prev->next = (t)->next; \
388 (void) free((void *) t)
391 free_pattern(jugglestruct *sp) {
392 if (sp->head != NULL) {
393 while (sp->head->next != sp->head) {
394 Trajectory *t = sp->head->next;
396 REMOVE(t); /* don't eliminate t */
398 (void) free((void *) sp->head);
399 sp->head = (Trajectory *) NULL;
404 free_juggle(jugglestruct *sp)
406 if (sp->trace != NULL) {
407 (void) free((void *) sp->trace);
408 sp->trace = (XPoint *) NULL;
414 add_throw(jugglestruct *sp, char type, int h, Notation n)
418 INSERT_AFTER(t, sp->head->prev);
430 /* add a Thratch to the performance */
432 program(ModeInfo *mi, const char *patn, int repeat)
434 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
440 if (MI_IS_VERBOSE(mi)) {
441 (void) fprintf(stderr, "%s x %d\n", patn, repeat);
444 for(i=0; i < repeat; i++) {
449 for(p=patn; *p; p++) {
450 if (*p >= '0' && *p <='9') {
452 h = 10*h + (*p - '0');
454 Notation nn = notation;
456 case '[': /* begin Adam notation */
459 case '-': /* Inside throw */
460 case '+': /* Outside throw */
461 case '=': /* Cross throw */
462 case '&': /* Cross catch */
463 case 'x': /* Cross throw and catch */
464 case '_': /* Bounce */
467 case '*': /* Lose ball */
471 case ']': /* end Adam notation */
476 if (!add_throw(sp, type, h, notation))
485 (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
491 if (!add_throw(sp, type, h, notation))
506 4 4 1 3 12 2 4 1 4 4 13 0 3 1
509 #define BOUNCEOVER 10
512 adam(jugglestruct *sp)
515 for(t = sp->head->next; t != sp->head; t = t->next) {
516 if (t->status == ATCH) {
519 for(p = t->next; a > 0 && p != sp->head; p = p->next) {
520 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
525 if(t->height > BOUNCEOVER && t->posn == ' '){
526 t->posn = '_'; /* high defaults can be bounced */
530 (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
536 /* Split Thratch notation into explicit throws and catches.
537 Usually Catch follows Throw in same hand, but take care of special
540 /* ..n1.. -> .. LTn RT1 LC RC .. */
541 /* ..nm.. -> .. LTn LC RTm RC .. */
544 part(jugglestruct *sp)
546 Trajectory *t, *nt, *p;
547 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
549 for (t = sp->head->next; t != sp->head; t = t->next) {
550 if (t->status > THRATCH) {
552 } else if (t->status == THRATCH) {
555 /* plausibility check */
556 if (t->height <= 2 && t->posn == '_') {
557 t->posn = ' '; /* no short bounces */
559 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
560 t->posn = ' '; /* 1's need close catches */
565 case ' ': /* fall through */
566 case '-': posn = '-'; t->posn = '+'; break;
567 case '+': posn = '+'; t->posn = '-'; break;
568 case '=': posn = '='; t->posn = '+'; break;
569 case '&': posn = '+'; t->posn = '='; break;
570 case 'x': posn = '='; t->posn = '='; break;
571 case '_': posn = '_'; t->posn = '-'; break;
572 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
574 hand = (Hand) ((hand + 1) % 2);
579 if (t->height == 1) {
580 p = p->prev; /* early throw */
586 nt->height = t->height;
594 /* Connnect up throws and catches to figure out which ball goes where.
595 Do the same with the juggler's hands. */
600 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
603 for (t = sp->head->next; t != sp->head; t = t->next) {
604 if (t->status == ACTION) {
606 (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
609 (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
612 if (t->action == THROW) {
613 if (t->type == Empty) {
614 if (MI_NPIXELS(mi) > 2) {
615 t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
617 t->spin = NRAND(5) - 2;
618 t->degree_offset = NRAND(360);
619 t->divisions = 2 * ((LRAND() & 1) + 1);
622 /* search forward for next hand catch */
623 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
624 if (p->action == CATCH) {
625 if (t->handlink == NULL && p->hand == t->hand) {
626 t->handlink = p; /* next catch in this hand */
634 /* search forward for next ball catch */
635 for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
636 if (p->action == CATCH) {
637 if (t->balllink == NULL && --h < 1) { /* caught */
639 if (p->type == Full) {
643 t->balllink = p; /* complete trajectory */
645 p->color = t->color; /* accept catch */
647 p->degree_offset = t->degree_offset;
648 p->divisions = t->divisions;
653 t->type = Empty; /* thrown */
654 } else if (t->action == CATCH) {
655 /* search forward for next throw from this hand */
656 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
657 if (p->action == THROW && p->hand == t->hand) {
658 p->type = t->type; /* pass ball */
659 p->color = t->color; /* pass color */
660 p->spin = NRAND(5) - 2;
661 p->degree_offset = NRAND(360);
662 p->divisions = 2 * ((LRAND() & 1) + 1);
667 t->status = LINKEDACTION;
672 /* Convert hand position symbols into actual time/space coordinates */
674 positions(jugglestruct *sp)
678 for (t = sp->head->next; t != sp->head; t = t->next) {
679 if (t->status == PTHRATCH) {
681 } else if (t->status == LINKEDACTION) {
685 if (t->action == CATCH) {
686 if (t->type == Empty) {
687 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
689 now += THROW_CATCH_INTERVAL; /* successful catch */
692 now += (int) CATCH_THROW_INTERVAL; /* throws are very short */
699 case '-': xo = SX - POSE; break;
701 case '+': xo = SX + POSE; break;
702 case '=': xo = - SX - POSE; yo += 2 * POSE; break;
703 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
705 t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
708 t->status = PTHRATCH;
714 /* Private physics functions */
717 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
726 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
727 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
732 s.c = (3*a*t0 - 2*b)*t0 + c;
733 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
738 makeSplinePair(Spline *s1, Spline *s2,
739 int x0, double dx0, int t0,
741 int x2, double dx2, int t2)
744 double t21, t10, t20, dx1;
750 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
751 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
753 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
754 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
758 /* Turn abstract timings into physically appropriate ball trajectories. */
760 projectile(jugglestruct *sp)
763 for (t = sp->head->next; t != sp->head; t = t->next) {
764 if (t->status != PTHRATCH) {
767 if (t->action == THROW) {
768 if (t->balllink != NULL) {
769 if (t->posn == '_') { /* Bounce once */
770 double tc, y0, yf, yc, tb, e, i;
772 tc = t->balllink->start - t->start;
777 e = 1; /* permissible error in yc */
781 yt = height at catch time after one bounce
782 one or three roots according to timing
783 find one by interval bisection
786 for(i = tc / 2; i > 0; i/=2){
789 (void) fprintf(stderr, "div by zero!\n");
792 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
794 yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
797 }else if(yt < yc - e){
807 t->dx = (t->balllink->x - t->x) / tc;
809 /* ball follows parabola down */
810 INSERT_AFTER(n, t->prev);
812 n->finish = (int) (t->start + tb);
816 n->degree_offset = t->degree_offset;
817 n->divisions = t->divisions;
818 n->status = PREDICTOR;
820 t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
822 /* Represent parabola as a degenerate spline -
823 linear in x, quadratic in y */
827 n->xp.d = -t->dx*t0 + t->x;
830 n->yp.c = -sp->Gr*t0 + t->dy;
831 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
834 /* ball follows parabola up */
835 INSERT_AFTER(n, t->prev);
836 n->start = (int) (t0 + tb);
837 n->finish = (int) (t0 + tc);
841 n->degree_offset = t->degree_offset;
842 n->divisions = t->divisions;
843 n->status = PREDICTOR;
848 n->xp.d = -t->dx*t0 + t->x;
850 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
852 /* Represent parabola as a degenerate spline -
853 linear in x, quadratic in y */
856 n->yp.c = -sp->Gr*t0 - COR*dy;
857 n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
860 t->status = BPREDICTOR;
865 /* ball follows parabola */
866 INSERT_AFTER(n, t->prev);
868 n->finish = t->balllink->start;
872 n->degree_offset = t->degree_offset;
873 n->divisions = t->divisions;
874 n->status = PREDICTOR;
877 dt = t->balllink->start - t->start;
878 t->dx = (t->balllink->x - t->x) / dt;
879 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
881 /* Represent parabola as a degenerate spline -
882 linear in x, quadratic in y */
886 n->xp.d = -t->dx*t0 + t->x;
889 n->yp.c = -sp->Gr*t0 + t->dy;
890 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
893 t->status = BPREDICTOR;
895 } else { /* Zero Throw */
896 t->status = BPREDICTOR;
903 /* Turn abstract hand motions into cubic splines. */
905 hands(jugglestruct *sp)
907 Trajectory *t, *u, *v;
908 for (t = sp->head->next; t != sp->head; t = t->next) {
909 /* no throw => no velocity */
910 if (t->status != BPREDICTOR) {
915 if (u == NULL) { /* no next catch */
919 if (v == NULL) { /* no next throw */
923 /* double spline takes hand from throw, thru catch, to
926 t->finish = u->start;
927 t->status = PREDICTOR;
929 u->finish = v->start;
930 u->status = PREDICTOR;
932 (void) makeSplinePair(&t->xp, &u->xp,
933 t->x, t->dx, t->start,
935 v->x, v->dx, v->start);
936 (void) makeSplinePair(&t->yp, &u->yp,
937 t->y, t->dy, t->start,
939 v->y, v->dy, v->start);
941 t->status = PREDICTOR;
945 /* Given target x, y find_elbow puts hand at target if possible,
946 * otherwise makes hand point to the target */
948 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
952 h2 = x*x + y*y + z*z;
953 if (h2 > 4*ARMLENGTH*ARMLENGTH) {
954 t = ARMLENGTH/sqrt(h2);
955 e->x = (short) (t*x);
956 e->y = (short) (t*y);
960 r = sqrt((double)(x*x + z*z));
961 t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
962 e->x = (short) (x*(1 - y*t/r)/2);
963 e->y = (short) ((y + r*t)/2);
969 /* NOTE: returned x, y adjusted for arm reach */
971 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
973 Display *dpy = MI_DISPLAY(mi);
974 Window win = MI_WINDOW(mi);
976 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
978 int sig = (side == LEFT) ? 1 : -1;
980 XSetLineAttributes(dpy, gc,
981 ARMWIDTH, LineSolid, CapRound, JoinRound);
982 if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
984 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
985 find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
986 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
987 *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
988 *y = sp->arm[side][0].y = sp->cy + SY + h.y;
989 sp->arm[side][1].x = sp->cx + sig*SX + e.x;
990 sp->arm[side][1].y = sp->cy + SY + e.y;
992 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
993 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
994 XSetLineAttributes(dpy, gc,
995 1, LineSolid, CapNotLast, JoinRound);
999 draw_figure(ModeInfo * mi)
1001 Display *dpy = MI_DISPLAY(mi);
1002 Window win = MI_WINDOW(mi);
1004 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1006 XSetLineAttributes(dpy, gc,
1007 ARMWIDTH, LineSolid, CapRound, JoinRound);
1008 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1009 XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1010 XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1011 XDrawArc(dpy, win, gc,
1012 sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1013 XSetLineAttributes(dpy, gc,
1014 1, LineSolid, CapNotLast, JoinRound);
1018 /* dumps a human-readable rendition of the current state of the juggle
1019 pipeline to stderr for debugging */
1022 dump(jugglestruct *sp)
1026 for (t = sp->head->next; t != sp->head; t = t->next) {
1027 switch (t->status) {
1029 (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1032 (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1034 t->hand ? 'R' : 'L',
1035 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1039 (void) fprintf(stderr, "L %c%c%c%d %d\n",
1042 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1043 t->height, t->color);
1046 (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1048 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1049 t->height, t->type, t->color,
1050 t->start, t->finish);
1053 (void) fprintf(stderr, "P %c %2d %6d %6d %g\n",
1054 t->type == Ball?'b':t->type == Empty?'e':'f',
1056 t->start, t->finish, t->yp.c);
1059 (void) fprintf(stderr, "status %d not implemented\n", t->status);
1066 static int get_num_balls(const char *j)
1071 for (p = j; *p; p++) {
1072 if (*p >= '0' && *p <='9') { /* digit */
1073 h = 10*h + (*p - '0');
1088 static int compare_num_balls(const void *p1, const void *p2)
1090 int i = get_num_balls(((patternstruct*)p1)->pattern);
1091 int j = get_num_balls(((patternstruct*)p2)->pattern);
1105 /* Public functions */
1108 release_juggle(ModeInfo * mi)
1110 if (juggles != NULL) {
1113 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1114 free_juggle(&juggles[screen]);
1115 (void) free((void *) juggles);
1116 juggles = (jugglestruct *) NULL;
1118 if (patternindex != NULL) {
1119 (void) free((void *) patternindex);
1120 patternindex = (PatternIndex *) NULL;
1125 init_juggle(ModeInfo * mi)
1129 XPoint figure1[FIGURE1];
1130 XSegment figure2[FIGURE2];
1131 if (pattern != NULL && *pattern == '.') {
1134 if (pattern == NULL && patternindex == NULL) {
1135 /* pattern list needs indexing */
1137 int nelements = sizeof(portfolio)/sizeof(patternstruct);
1141 /* sort according to number of balls */
1142 qsort((void*)portfolio, nelements,
1143 sizeof(patternstruct), compare_num_balls);
1145 /* last pattern has most balls */
1146 maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1147 /* allocate index */
1148 if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1149 sizeof (PatternIndex))) == NULL) {
1153 /* run through sorted list, indexing start of each group
1154 and number in group */
1156 for (i = 0; i < nelements; i++) {
1157 int b = get_num_balls(portfolio[i].pattern);
1159 if (MI_IS_VERBOSE(mi)) {
1160 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1161 numpat, maxballs, (numpat == 1) ? "" : "s");
1163 patternindex[maxballs].number = numpat;
1166 patternindex[maxballs].start = i;
1171 if (MI_IS_VERBOSE(mi)) {
1172 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1173 numpat, maxballs, (numpat == 1) ? "" : "s");
1175 patternindex[maxballs].number = numpat;
1178 if (juggles == NULL) { /* allocate jugglestruct */
1179 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1180 sizeof (jugglestruct))) == NULL) {
1185 sp = &juggles[MI_SCREEN(mi)];
1189 if (MI_IS_FULLRANDOM(mi)) {
1190 sp->solid = (Bool) (LRAND() & 1);
1192 sp->uni = (Bool) (LRAND() & 1);
1201 sp->width = MI_WIDTH(mi);
1202 sp->height = MI_HEIGHT(mi);
1203 sp->count = ABS(MI_COUNT(mi));
1206 sp->scale = sp->height / 480.0;
1207 /* vary x a little so the juggler does not burn the screen */
1208 sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1209 sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1210 /* "7" hits top of screen */
1211 sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1213 figure1[0].x = LHIPX, figure1[0].y = HIPY;
1214 figure1[1].x = 0, figure1[1].y = WSTY;
1215 figure1[2].x = SX, figure1[2].y = SY;
1216 figure1[3].x = -SX, figure1[3].y = SY;
1217 figure1[4].x = 0, figure1[4].y = WSTY;
1218 figure1[5].x = RHIPX, figure1[5].y = HIPY;
1219 figure1[6].x = LHIPX, figure1[6].y = HIPY;
1220 figure2[0].x1 = 0, figure2[0].y1 = SY,
1221 figure2[0].x2 = 0, figure2[0].y2 = NEY;
1222 figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1223 figure2[1].x2 = LFX, figure2[1].y2 = FY;
1224 figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1225 figure2[2].x2 = RFX, figure2[2].y2 = FY;
1228 for (i = 0; i < FIGURE1; i++) {
1229 sp->figure_path[i].x = figure1[i].x + sp->cx;
1230 sp->figure_path[i].y = figure1[i].y + sp->cy;
1233 for (i = 0; i < FIGURE2; i++) {
1234 sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1235 sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1236 sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1237 sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1240 sp->arm[LEFT][2].x = sp->cx + SX;
1241 sp->arm[LEFT][2].y = sp->cy + SY;
1242 sp->arm[RIGHT][2].x = sp->cx - SX;
1243 sp->arm[RIGHT][2].y = sp->cy + SY;
1245 if (sp->trace == NULL) {
1246 if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1252 /* Clear the background. */
1257 /* record start time */
1258 sp->begintime = time(NULL);
1262 /* create circular list */
1263 INSERT_AFTER_TOP(sp->head, sp->head);
1265 /* generate pattern */
1266 if (pattern == NULL) {
1269 #define MAXREPEAT 30
1270 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1271 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1274 int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1275 while (count < MI_CYCLES(mi)) {
1276 char buf[MAXPAT * 3 + 3], *b = buf;
1278 int l = NRAND(MAXPAT) + 1;
1279 int t = NRAND(MAXREPEAT) + 1;
1281 { /* vary number of balls */
1282 int new_balls = num_balls;
1285 if (new_balls == 2) /* Do not juggle 2 that often */
1286 change = NRAND(2 + CHANGE_BIAS / 4);
1288 change = NRAND(2 + CHANGE_BIAS);
1299 if (new_balls < MINBALLS) {
1302 if (new_balls > MAXBALLS) {
1305 if (new_balls < num_balls) {
1306 if (!program(mi, "[*]", 1)) /* lose ball */
1309 num_balls = new_balls;
1313 if (NRAND(2) && patternindex[num_balls].number) {
1314 /* Pick from PortFolio */
1316 portfolio[patternindex[num_balls].start +
1317 NRAND(patternindex[num_balls].number)].pattern,
1321 /* Invent a new pattern */
1323 for(i = 0; i < l; i++){
1325 do { /* Triangular Distribution => high values more likely */
1326 m = NRAND(num_balls + 1);
1327 n = NRAND(num_balls + 1);
1329 if (n == num_balls) {
1332 switch(NRAND(6 + POSITION_BIAS)){
1333 case 0: /* Inside throw */
1335 case 1: /* Outside throw */
1337 case 2: /* Cross throw */
1339 case 3: /* Cross catch */
1341 case 4: /* Cross throw and catch */
1343 case 5: /* Bounce */
1355 if (!program(mi, buf, t))
1360 } else { /* pattern supplied in height or 'a' notation */
1361 if (!program(mi, pattern, MI_CYCLES(mi)))
1374 if (!projectile(sp))
1384 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1388 * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1389 * trace behind when erased
1391 #define ERASE_BALL(x,y) \
1392 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1393 XFillArc(dpy, window, gc, \
1394 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1395 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1398 #define ERASE_BALL(x,y) \
1399 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1400 XFillArc(dpy, window, gc, \
1401 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1402 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1406 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1408 Display *dpy = MI_DISPLAY(mi);
1409 Window window = MI_WINDOW(mi);
1411 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1414 XSetForeground(dpy, gc, color);
1415 if ((color == MI_WHITE_PIXEL(mi)) ||
1416 ((divisions != 2) && (divisions != 4)) || sp->solid) {
1417 XFillArc(dpy, window, gc,
1418 x - BALLRADIUS, y - BALLRADIUS,
1419 2*BALLRADIUS, 2*BALLRADIUS,
1423 offset = (int) (degree_offset * 64);
1424 if (divisions == 4) { /* 90 degree divisions */
1425 XFillArc(dpy, window, gc,
1426 x - BALLRADIUS, y - BALLRADIUS,
1427 2*BALLRADIUS, 2*BALLRADIUS,
1429 XFillArc(dpy, window, gc,
1430 x - BALLRADIUS, y - BALLRADIUS,
1431 2*BALLRADIUS, 2*BALLRADIUS,
1432 (offset + 11520) % 23040, 5760);
1433 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1434 XFillArc(dpy, window, gc,
1435 x - BALLRADIUS, y - BALLRADIUS,
1436 2*BALLRADIUS, 2*BALLRADIUS,
1437 (offset + 5760) % 23040, 5760);
1438 XFillArc(dpy, window, gc,
1439 x - BALLRADIUS, y - BALLRADIUS,
1440 2*BALLRADIUS, 2*BALLRADIUS,
1441 (offset + 17280) % 23040, 5760);
1442 } else { /* 180 degree divisions */
1443 XFillArc(dpy, window, gc,
1444 x - BALLRADIUS, y - BALLRADIUS,
1445 2*BALLRADIUS, 2*BALLRADIUS,
1447 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1448 XFillArc(dpy, window, gc,
1449 x - BALLRADIUS, y - BALLRADIUS,
1450 2*BALLRADIUS, 2*BALLRADIUS,
1451 (offset + 11520) % 23040, 11520);
1457 draw_juggle(ModeInfo * mi)
1459 Display *dpy = MI_DISPLAY(mi);
1460 Window window = MI_WINDOW(mi);
1467 if (juggles == NULL)
1469 sp = &juggles[MI_SCREEN(mi)];
1470 if (sp->trace == NULL)
1473 MI_IS_DRAWN(mi) = True;
1479 (void)gettimeofday(&tv, NULL);
1480 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
1482 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
1484 if (traj->status != PREDICTOR) {
1487 if (traj->start > future) {
1488 future = traj->start;
1490 if (sp->time < traj->start) { /* early */
1492 } else if (sp->time < traj->finish) { /* working */
1493 int x = (int) CUBIC(traj->xp, sp->time);
1494 int y = (int) CUBIC(traj->yp, sp->time);
1495 unsigned long color;
1497 if (MI_NPIXELS(mi) > 2) {
1498 color = MI_PIXEL(mi, traj->color);
1500 color = MI_WHITE_PIXEL(mi);
1502 if (traj->type == Empty || traj->type == Full) {
1503 draw_arm(mi, traj->hand, &x, &y);
1505 if (traj->type == Ball || traj->type == Full) {
1507 ERASE_BALL(sp->trace[sp->traceindex].x,
1508 sp->trace[sp->traceindex].y);
1509 sp->trace[sp->traceindex].x = traj->x;
1510 sp->trace[sp->traceindex].y = traj->y;
1511 if (++sp->traceindex >= trail) {
1515 ERASE_BALL(traj->x, traj->y);
1517 draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions);
1518 traj->degree_offset = traj->degree_offset +
1519 SPIN_DEGREES * traj->spin / sp->count;
1520 if (traj->degree_offset < 0.0)
1521 traj->degree_offset += 360.0;
1522 else if (traj->degree_offset >= 360.0)
1523 traj->degree_offset -= 360.0;
1527 } else { /* expired */
1528 Trajectory *n = traj;
1530 ERASE_BALL(traj->x, traj->y);
1536 /*** FIXME-BEGIN ***/
1537 /* pattern generator would refill here when necessary */
1541 if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */
1549 #endif /* MODE_juggle */