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 {&pattern, "pattern",
150 (char *) "Pattern", (char *) DEF_PATTERN, t_String},
151 {&trail, "trail", "Trail", DEF_TRAIL, t_Int},
153 {&uni, "uni", "Uni", DEF_UNI, t_Bool},
155 {&solid, "solid", "Solid", DEF_SOLID, t_Bool}
157 static OptionStruct desc[] =
159 {(char *) "-pattern string", (char *) "Cambridge Juggling Pattern"},
160 {(char *) "-trail num", (char *) "Trace Juggling Patterns"},
162 {(char *) "-/+uni", (char *) "Unicycle"},
164 {(char *) "-/+solid", (char *) "solid color (else its a 4 panel look (half white))"}
167 ModeSpecOpt juggle_opts =
168 {sizeof opts / sizeof opts[0], opts,
169 sizeof vars / sizeof vars[0], vars, desc};
172 ModStruct juggle_description = {
173 "juggle", "init_juggle", "draw_juggle", "release_juggle",
174 "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
175 10000, 150, 30, 1, 64, 1.0, "",
176 "Shows a Juggler, juggling", 0, NULL
182 #include <X11/unix_time.h>
186 #include <sys/time.h>
188 #if HAVE_SYS_SELECT_H
189 #include <sys/select.h>
194 #define ARMLENGTH ((int) (40.0 * sp->scale))
195 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
196 #define POSE ((int) (10.0 * sp->scale))
197 #define SX ((int) (25.0 * sp->scale))
198 #define SZ ((int) (25.0 * sp->scale))
199 #define SY ((int) (25.0 * sp->scale))
200 #define HIPY ((int) (85.0 * sp->scale))
201 #define RHIPX ((int) (-15.0 * sp->scale))
202 #define LHIPX ((int) (15.0 * sp->scale))
203 #define RFX ((int) (-25.0 * sp->scale))
204 #define LFX ((int) (25.0 * sp->scale))
205 #define FY ((int) (155.0 * sp->scale))
206 #define WSTY ((int) (65.0 * sp->scale))
207 #define NEY ((int) (15.0 * sp->scale))
208 #define HED ((int) (35.0 * sp->scale))
209 #define BALLRADIUS ARMWIDTH
212 #define TRACE_LENGTH 50
213 #define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */
218 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
221 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
223 #define THROW_CATCH_INTERVAL (sp->count)
224 #define THROW_NULL_INTERVAL (sp->count * 0.5)
225 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
226 #define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */
231 typedef enum {HEIGHT, ADAM} Notation;
232 typedef enum {Empty, Full, Ball} Throwable;
233 typedef enum {LEFT, RIGHT} Hand;
234 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
235 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
236 PREDICTOR} TrajectoryStatus;
238 typedef struct trajectory *TrajectoryPtr;
240 typedef struct {double a, b, c, d; } Spline;
242 typedef struct trajectory {
243 TrajectoryPtr prev, next; /* for building list */
244 TrajectoryStatus status;
258 double degree_offset;
259 TrajectoryPtr balllink;
260 TrajectoryPtr handlink;
264 double dx; /* initial velocity */
271 int x, y; /* current position */
286 XPoint figure_path[FIGURE1];
287 XSegment figure_segs[FIGURE2];
292 time_t begintime; /* seconds */
293 int time; /* millisecond timer */
297 static jugglestruct *juggles = (jugglestruct *) NULL;
312 static PatternIndex* patternindex = (PatternIndex *) NULL;
314 /* List of popular patterns, in any order */
315 static patternstruct portfolio[] = {
316 {(char *) "[+2 1]", (char *) "+3 1, Typical 2 ball juggler"},
317 {(char *) "[2 0]", (char *) "4 0, 2 balls 1 hand"},
318 {(char *) "[2 0 1]", (char *) "5 0 1"},
319 {(char *) "[+2 0 +2 0 0]", (char *) "+5 0 +5 0 0"},
320 {(char *) "[3]", (char *) "3, cascade"},
321 {(char *) "[+3]", (char *) "+3, reverse cascade"},
322 {(char *) "[=3]", (char *) "=3, cascade under arm"},
323 {(char *) "[&3]", (char *) "&3, cascade catching under arm"},
324 {(char *) "[_3]", (char *) "_3, bouncing cascade"},
325 {(char *) "[+3 x3 =3]", (char *) "+3 x3 =3, Mill's mess"},
326 {(char *) "[3 2 1]", (char *) "5 3 1"},
327 {(char *) "[3 3 1]", (char *) "4 4 1"},
328 {(char *) "[3 1 2]", (char *) "6 1 2, See-saw"},
329 {(char *) "[=3 3 1 2]", (char *) "=4 5 1 2"},
330 {(char *) "[=3 2 2 3 1 2]", (char *) "=6 2 2 5 1 2, =4 5 1 2 stretched"},
331 {(char *) "[+3 3 1 3]", (char *) "+4 4 1 3, anemic shower box"},
332 {(char *) "[3 3 1]", (char *) "4 4 1"},
333 {(char *) "[+3 2 3]", (char *) "+4 2 3"},
334 {(char *) "[+3 1]", (char *) "+5 1, 3 shower"},
335 {(char *) "[_3 1]", (char *) "_5 1, bouncing 3 shower"},
336 {(char *) "[3 0 3 0 3]", (char *) "5 0 5 0 5, shake 3 out of 5"},
337 {(char *) "[3 3 3 0 0]", (char *) "5 5 5 0 0, flash 3 out of 5"},
338 {(char *) "[3 3 0]", (char *) "4 5 0, complete waste of a 5 ball juggler"},
339 {(char *) "[3 3 3 0 0 0 0]", (char *) "7 7 7 0 0 0 0, 3 flash"},
340 {(char *) "[+3 0 +3 0 +3 0 0]", (char *) "+7 0 +7 0 +7 0 0"},
341 {(char *) "[4]", (char *) "4, 4 cascade"},
342 {(char *) "[+4 3]", (char *) "+5 3, 4 ball half shower"},
343 {(char *) "[4 4 2]", (char *) "5 5 2"},
344 {(char *) "[+4 4 4 +4]", (char *) "+4 4 4 +4, 4 columns"},
345 {(char *) "[4 3 +4]", (char *) "5 3 +4"},
346 {(char *) "[+4 1]", (char *) "+7 1, 4 shower"},
347 {(char *) "[4 4 4 4 0]", (char *) "5 5 5 5 0, learning 5"},
348 {(char *) "[5]", (char *) "5, 5 cascade"},
349 {(char *) "[_5 _5 _5 _5 _5 5 5 5 5 5]", (char *) "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
350 {(char *) "[+5 x5 =5]", (char *) "+5 x5 =5, Mill's mess for 5"},
351 {(char *) "[6]", (char *) "6, 6 cascade"},
352 {(char *) "[7]", (char *) "7, 7 cascade"},
353 {(char *) "[_7]", (char *) "_7, bouncing 7 cascade"},
356 /* Private Functions */
358 /* list management */
360 /* t receives trajectory to be created. ot must point to an existing
361 trajectory or be identical to t to start a new list. */
362 #define INSERT_AFTER_TOP(t, ot) \
363 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
364 {free_juggle(sp); return;} \
365 (t)->next = (ot)->next; \
368 (t)->next->prev = (t)
369 #define INSERT_AFTER(t, ot) \
370 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
371 {free_juggle(sp); return False;} \
372 (t)->next = (ot)->next; \
375 (t)->next->prev = (t)
378 /* t must point to an existing trajectory. t must not be an
379 expression ending ->next or ->prev */
381 (t)->next->prev = (t)->prev; \
382 (t)->prev->next = (t)->next; \
383 (void) free((void *) t)
386 free_pattern(jugglestruct *sp) {
387 if (sp->head != NULL) {
388 while (sp->head->next != sp->head) {
389 Trajectory *t = sp->head->next;
391 REMOVE(t); /* don't eliminate t */
393 (void) free((void *) sp->head);
394 sp->head = (Trajectory *) NULL;
399 free_juggle(jugglestruct *sp)
401 if (sp->trace != NULL) {
402 (void) free((void *) sp->trace);
403 sp->trace = (XPoint *) NULL;
409 add_throw(jugglestruct *sp, char type, int h, Notation n)
413 INSERT_AFTER(t, sp->head->prev);
425 /* add a Thratch to the performance */
427 program(ModeInfo *mi, const char *patn, int repeat)
429 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
435 if (MI_IS_VERBOSE(mi)) {
436 (void) fprintf(stderr, "%s x %d\n", patn, repeat);
439 for(i=0; i < repeat; i++) {
444 for(p=patn; *p; p++) {
445 if (*p >= '0' && *p <='9') {
447 h = 10*h + (*p - '0');
449 Notation nn = notation;
451 case '[': /* begin Adam notation */
454 case '-': /* Inside throw */
455 case '+': /* Outside throw */
456 case '=': /* Cross throw */
457 case '&': /* Cross catch */
458 case 'x': /* Cross throw and catch */
459 case '_': /* Bounce */
462 case '*': /* Lose ball */
466 case ']': /* end Adam notation */
471 if (!add_throw(sp, type, h, notation))
480 (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
486 if (!add_throw(sp, type, h, notation))
501 4 4 1 3 12 2 4 1 4 4 13 0 3 1
504 #define BOUNCEOVER 10
507 adam(jugglestruct *sp)
510 for(t = sp->head->next; t != sp->head; t = t->next) {
511 if (t->status == ATCH) {
514 for(p = t->next; a > 0 && p != sp->head; p = p->next) {
515 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
520 if(t->height > BOUNCEOVER && t->posn == ' '){
521 t->posn = '_'; /* high defaults can be bounced */
525 (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
531 /* Split Thratch notation into explicit throws and catches.
532 Usually Catch follows Throw in same hand, but take care of special
535 /* ..n1.. -> .. LTn RT1 LC RC .. */
536 /* ..nm.. -> .. LTn LC RTm RC .. */
539 part(jugglestruct *sp)
541 Trajectory *t, *nt, *p;
542 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
544 for (t = sp->head->next; t != sp->head; t = t->next) {
545 if (t->status > THRATCH) {
547 } else if (t->status == THRATCH) {
550 /* plausibility check */
551 if (t->height <= 2 && t->posn == '_') {
552 t->posn = ' '; /* no short bounces */
554 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
555 t->posn = ' '; /* 1's need close catches */
560 case ' ': /* fall through */
561 case '-': posn = '-'; t->posn = '+'; break;
562 case '+': posn = '+'; t->posn = '-'; break;
563 case '=': posn = '='; t->posn = '+'; break;
564 case '&': posn = '+'; t->posn = '='; break;
565 case 'x': posn = '='; t->posn = '='; break;
566 case '_': posn = '_'; t->posn = '-'; break;
567 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
569 hand = (Hand) ((hand + 1) % 2);
574 if (t->height == 1) {
575 p = p->prev; /* early throw */
581 nt->height = t->height;
589 /* Connnect up throws and catches to figure out which ball goes where.
590 Do the same with the juggler's hands. */
595 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
598 for (t = sp->head->next; t != sp->head; t = t->next) {
599 if (t->status == ACTION) {
601 (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
604 (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
607 if (t->action == THROW) {
608 if (t->type == Empty) {
609 if (MI_NPIXELS(mi) > 2) {
610 t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
612 t->spin = NRAND(5) - 2;
613 t->degree_offset = NRAND(360);
614 t->divisions = 2 * ((LRAND() & 1) + 1);
617 /* search forward for next hand catch */
618 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
619 if (p->action == CATCH) {
620 if (t->handlink == NULL && p->hand == t->hand) {
621 t->handlink = p; /* next catch in this hand */
629 /* search forward for next ball catch */
630 for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
631 if (p->action == CATCH) {
632 if (t->balllink == NULL && --h < 1) { /* caught */
634 if (p->type == Full) {
638 t->balllink = p; /* complete trajectory */
640 p->color = t->color; /* accept catch */
642 p->degree_offset = t->degree_offset;
643 p->divisions = t->divisions;
648 t->type = Empty; /* thrown */
649 } else if (t->action == CATCH) {
650 /* search forward for next throw from this hand */
651 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
652 if (p->action == THROW && p->hand == t->hand) {
653 p->type = t->type; /* pass ball */
654 p->color = t->color; /* pass color */
655 p->spin = NRAND(5) - 2;
656 p->degree_offset = NRAND(360);
657 p->divisions = 2 * ((LRAND() & 1) + 1);
662 t->status = LINKEDACTION;
667 /* Convert hand position symbols into actual time/space coordinates */
669 positions(jugglestruct *sp)
673 for (t = sp->head->next; t != sp->head; t = t->next) {
674 if (t->status == PTHRATCH) {
676 } else if (t->status == LINKEDACTION) {
680 if (t->action == CATCH) {
681 if (t->type == Empty) {
682 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
684 now += THROW_CATCH_INTERVAL; /* successful catch */
687 now += (int) CATCH_THROW_INTERVAL; /* throws are very short */
694 case '-': xo = SX - POSE; break;
696 case '+': xo = SX + POSE; break;
697 case '=': xo = - SX - POSE; yo += 2 * POSE; break;
698 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
700 t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
703 t->status = PTHRATCH;
709 /* Private physics functions */
712 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
721 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
722 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
727 s.c = (3*a*t0 - 2*b)*t0 + c;
728 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
733 makeSplinePair(Spline *s1, Spline *s2,
734 int x0, double dx0, int t0,
736 int x2, double dx2, int t2)
739 double t21, t10, t20, dx1;
745 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
746 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
748 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
749 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
753 /* Turn abstract timings into physically appropriate ball trajectories. */
755 projectile(jugglestruct *sp)
758 for (t = sp->head->next; t != sp->head; t = t->next) {
759 if (t->status != PTHRATCH) {
762 if (t->action == THROW) {
763 if (t->balllink != NULL) {
764 if (t->posn == '_') { /* Bounce once */
765 double tc, y0, yf, yc, tb, e, i;
767 tc = t->balllink->start - t->start;
772 e = 1; /* permissible error in yc */
776 yt = height at catch time after one bounce
777 one or three roots according to timing
778 find one by interval bisection
781 for(i = tc / 2; i > 0; i/=2){
784 (void) fprintf(stderr, "div by zero!\n");
787 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
789 yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
792 }else if(yt < yc - e){
802 t->dx = (t->balllink->x - t->x) / tc;
804 /* ball follows parabola down */
805 INSERT_AFTER(n, t->prev);
807 n->finish = (int) (t->start + tb);
811 n->degree_offset = t->degree_offset;
812 n->divisions = t->divisions;
813 n->status = PREDICTOR;
815 t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
817 /* Represent parabola as a degenerate spline -
818 linear in x, quadratic in y */
822 n->xp.d = -t->dx*t0 + t->x;
825 n->yp.c = -sp->Gr*t0 + t->dy;
826 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
829 /* ball follows parabola up */
830 INSERT_AFTER(n, t->prev);
831 n->start = (int) (t0 + tb);
832 n->finish = (int) (t0 + tc);
836 n->degree_offset = t->degree_offset;
837 n->divisions = t->divisions;
838 n->status = PREDICTOR;
843 n->xp.d = -t->dx*t0 + t->x;
845 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
847 /* Represent parabola as a degenerate spline -
848 linear in x, quadratic in y */
851 n->yp.c = -sp->Gr*t0 - COR*dy;
852 n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
855 t->status = BPREDICTOR;
860 /* ball follows parabola */
861 INSERT_AFTER(n, t->prev);
863 n->finish = t->balllink->start;
867 n->degree_offset = t->degree_offset;
868 n->divisions = t->divisions;
869 n->status = PREDICTOR;
872 dt = t->balllink->start - t->start;
873 t->dx = (t->balllink->x - t->x) / dt;
874 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
876 /* Represent parabola as a degenerate spline -
877 linear in x, quadratic in y */
881 n->xp.d = -t->dx*t0 + t->x;
884 n->yp.c = -sp->Gr*t0 + t->dy;
885 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
888 t->status = BPREDICTOR;
890 } else { /* Zero Throw */
891 t->status = BPREDICTOR;
898 /* Turn abstract hand motions into cubic splines. */
900 hands(jugglestruct *sp)
902 Trajectory *t, *u, *v;
903 for (t = sp->head->next; t != sp->head; t = t->next) {
904 /* no throw => no velocity */
905 if (t->status != BPREDICTOR) {
910 if (u == NULL) { /* no next catch */
914 if (v == NULL) { /* no next throw */
918 /* double spline takes hand from throw, thru catch, to
921 t->finish = u->start;
922 t->status = PREDICTOR;
924 u->finish = v->start;
925 u->status = PREDICTOR;
927 (void) makeSplinePair(&t->xp, &u->xp,
928 t->x, t->dx, t->start,
930 v->x, v->dx, v->start);
931 (void) makeSplinePair(&t->yp, &u->yp,
932 t->y, t->dy, t->start,
934 v->y, v->dy, v->start);
936 t->status = PREDICTOR;
940 /* Given target x, y find_elbow puts hand at target if possible,
941 * otherwise makes hand point to the target */
943 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
947 h2 = x*x + y*y + z*z;
948 if (h2 > 4*ARMLENGTH*ARMLENGTH) {
949 t = ARMLENGTH/sqrt(h2);
950 e->x = (short) (t*x);
951 e->y = (short) (t*y);
955 r = sqrt((double)(x*x + z*z));
956 t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
957 e->x = (short) (x*(1 - y*t/r)/2);
958 e->y = (short) ((y + r*t)/2);
964 /* NOTE: returned x, y adjusted for arm reach */
966 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
968 Display *dpy = MI_DISPLAY(mi);
969 Window win = MI_WINDOW(mi);
971 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
973 int sig = (side == LEFT) ? 1 : -1;
975 XSetLineAttributes(dpy, gc,
976 ARMWIDTH, LineSolid, CapRound, JoinRound);
977 if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
979 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
980 find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
981 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
982 *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
983 *y = sp->arm[side][0].y = sp->cy + SY + h.y;
984 sp->arm[side][1].x = sp->cx + sig*SX + e.x;
985 sp->arm[side][1].y = sp->cy + SY + e.y;
987 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
988 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
989 XSetLineAttributes(dpy, gc,
990 1, LineSolid, CapNotLast, JoinRound);
994 draw_figure(ModeInfo * mi)
996 Display *dpy = MI_DISPLAY(mi);
997 Window win = MI_WINDOW(mi);
999 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1001 XSetLineAttributes(dpy, gc,
1002 ARMWIDTH, LineSolid, CapRound, JoinRound);
1003 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1004 XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1005 XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1006 XDrawArc(dpy, win, gc,
1007 sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1008 XSetLineAttributes(dpy, gc,
1009 1, LineSolid, CapNotLast, JoinRound);
1013 /* dumps a human-readable rendition of the current state of the juggle
1014 pipeline to stderr for debugging */
1017 dump(jugglestruct *sp)
1021 for (t = sp->head->next; t != sp->head; t = t->next) {
1022 switch (t->status) {
1024 (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1027 (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1029 t->hand ? 'R' : 'L',
1030 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1034 (void) fprintf(stderr, "L %c%c%c%d %d\n",
1037 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1038 t->height, t->color);
1041 (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1043 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1044 t->height, t->type, t->color,
1045 t->start, t->finish);
1048 (void) fprintf(stderr, "P %c %2d %6d %6d %g\n",
1049 t->type == Ball?'b':t->type == Empty?'e':'f',
1051 t->start, t->finish, t->yp.c);
1054 (void) fprintf(stderr, "status %d not implemented\n", t->status);
1061 static int get_num_balls(const char *j)
1066 for (p = j; *p; p++) {
1067 if (*p >= '0' && *p <='9') { /* digit */
1068 h = 10*h + (*p - '0');
1083 static int compare_num_balls(const void *p1, const void *p2)
1085 int i = get_num_balls(((patternstruct*)p1)->pattern);
1086 int j = get_num_balls(((patternstruct*)p2)->pattern);
1100 /* Public functions */
1103 release_juggle(ModeInfo * mi)
1105 if (juggles != NULL) {
1108 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1109 free_juggle(&juggles[screen]);
1110 (void) free((void *) juggles);
1111 juggles = (jugglestruct *) NULL;
1113 if (patternindex != NULL) {
1114 (void) free((void *) patternindex);
1115 patternindex = (PatternIndex *) NULL;
1120 init_juggle(ModeInfo * mi)
1124 XPoint figure1[FIGURE1];
1125 XSegment figure2[FIGURE2];
1126 if (pattern != NULL && *pattern == '.') {
1129 if (pattern == NULL && patternindex == NULL) {
1130 /* pattern list needs indexing */
1131 int nelements = sizeof(portfolio)/sizeof(patternstruct);
1135 /* sort according to number of balls */
1136 qsort((void*)portfolio, nelements,
1137 sizeof(patternstruct), compare_num_balls);
1139 /* last pattern has most balls */
1140 maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1141 /* allocate index */
1142 if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1143 sizeof (PatternIndex))) == NULL) {
1147 /* run through sorted list, indexing start of each group
1148 and number in group */
1150 for (i = 0; i < nelements; i++) {
1151 int b = get_num_balls(portfolio[i].pattern);
1153 if (MI_IS_VERBOSE(mi)) {
1154 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1155 numpat, maxballs, (numpat == 1) ? "" : "s");
1157 patternindex[maxballs].number = numpat;
1160 patternindex[maxballs].start = i;
1165 if (MI_IS_VERBOSE(mi)) {
1166 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1167 numpat, maxballs, (numpat == 1) ? "" : "s");
1169 patternindex[maxballs].number = numpat;
1172 if (juggles == NULL) { /* allocate jugglestruct */
1173 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1174 sizeof (jugglestruct))) == NULL) {
1179 sp = &juggles[MI_SCREEN(mi)];
1183 if (MI_IS_FULLRANDOM(mi)) {
1184 sp->solid = (Bool) (LRAND() & 1);
1186 sp->uni = (Bool) (LRAND() & 1);
1195 sp->width = MI_WIDTH(mi);
1196 sp->height = MI_HEIGHT(mi);
1197 sp->count = ABS(MI_COUNT(mi));
1200 sp->scale = sp->height / 480.0;
1201 /* vary x a little so the juggler does not burn the screen */
1202 sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1203 sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1204 /* "7" hits top of screen */
1205 sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1207 figure1[0].x = LHIPX, figure1[0].y = HIPY;
1208 figure1[1].x = 0, figure1[1].y = WSTY;
1209 figure1[2].x = SX, figure1[2].y = SY;
1210 figure1[3].x = -SX, figure1[3].y = SY;
1211 figure1[4].x = 0, figure1[4].y = WSTY;
1212 figure1[5].x = RHIPX, figure1[5].y = HIPY;
1213 figure1[6].x = LHIPX, figure1[6].y = HIPY;
1214 figure2[0].x1 = 0, figure2[0].y1 = SY,
1215 figure2[0].x2 = 0, figure2[0].y2 = NEY;
1216 figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1217 figure2[1].x2 = LFX, figure2[1].y2 = FY;
1218 figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1219 figure2[2].x2 = RFX, figure2[2].y2 = FY;
1222 for (i = 0; i < FIGURE1; i++) {
1223 sp->figure_path[i].x = figure1[i].x + sp->cx;
1224 sp->figure_path[i].y = figure1[i].y + sp->cy;
1227 for (i = 0; i < FIGURE2; i++) {
1228 sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1229 sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1230 sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1231 sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1234 sp->arm[LEFT][2].x = sp->cx + SX;
1235 sp->arm[LEFT][2].y = sp->cy + SY;
1236 sp->arm[RIGHT][2].x = sp->cx - SX;
1237 sp->arm[RIGHT][2].y = sp->cy + SY;
1239 if (sp->trace == NULL) {
1240 if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1246 /* Clear the background. */
1251 /* record start time */
1252 sp->begintime = time(NULL);
1256 /* create circular list */
1257 INSERT_AFTER_TOP(sp->head, sp->head);
1259 /* generate pattern */
1260 if (pattern == NULL) {
1263 #define MAXREPEAT 30
1264 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1265 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1268 int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1269 while (count < MI_CYCLES(mi)) {
1270 char buf[MAXPAT * 3 + 3], *b = buf;
1272 int l = NRAND(MAXPAT) + 1;
1273 int t = NRAND(MAXREPEAT) + 1;
1275 { /* vary number of balls */
1276 int new_balls = num_balls;
1279 if (new_balls == 2) /* Do not juggle 2 that often */
1280 change = NRAND(2 + CHANGE_BIAS / 4);
1282 change = NRAND(2 + CHANGE_BIAS);
1293 if (new_balls < MINBALLS) {
1296 if (new_balls > MAXBALLS) {
1299 if (new_balls < num_balls) {
1300 if (!program(mi, "[*]", 1)) /* lose ball */
1303 num_balls = new_balls;
1307 if (NRAND(2) && patternindex[num_balls].number) {
1308 /* Pick from PortFolio */
1310 portfolio[patternindex[num_balls].start +
1311 NRAND(patternindex[num_balls].number)].pattern,
1315 /* Invent a new pattern */
1317 for(i = 0; i < l; i++){
1319 do { /* Triangular Distribution => high values more likely */
1320 m = NRAND(num_balls + 1);
1321 n = NRAND(num_balls + 1);
1323 if (n == num_balls) {
1326 switch(NRAND(6 + POSITION_BIAS)){
1327 case 0: /* Inside throw */
1329 case 1: /* Outside throw */
1331 case 2: /* Cross throw */
1333 case 3: /* Cross catch */
1335 case 4: /* Cross throw and catch */
1337 case 5: /* Bounce */
1349 if (!program(mi, buf, t))
1354 } else { /* pattern supplied in height or 'a' notation */
1355 if (!program(mi, pattern, MI_CYCLES(mi)))
1368 if (!projectile(sp))
1378 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1382 * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1383 * trace behind when erased
1385 #define ERASE_BALL(x,y) \
1386 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1387 XFillArc(dpy, window, gc, \
1388 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1389 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1392 #define ERASE_BALL(x,y) \
1393 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1394 XFillArc(dpy, window, gc, \
1395 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1396 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1400 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1402 Display *dpy = MI_DISPLAY(mi);
1403 Window window = MI_WINDOW(mi);
1405 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1408 XSetForeground(dpy, gc, color);
1409 if ((color == MI_WHITE_PIXEL(mi)) ||
1410 ((divisions != 2) && (divisions != 4)) || sp->solid) {
1411 XFillArc(dpy, window, gc,
1412 x - BALLRADIUS, y - BALLRADIUS,
1413 2*BALLRADIUS, 2*BALLRADIUS,
1417 offset = (int) (degree_offset * 64);
1418 if (divisions == 4) { /* 90 degree divisions */
1419 XFillArc(dpy, window, gc,
1420 x - BALLRADIUS, y - BALLRADIUS,
1421 2*BALLRADIUS, 2*BALLRADIUS,
1423 XFillArc(dpy, window, gc,
1424 x - BALLRADIUS, y - BALLRADIUS,
1425 2*BALLRADIUS, 2*BALLRADIUS,
1426 (offset + 11520) % 23040, 5760);
1427 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1428 XFillArc(dpy, window, gc,
1429 x - BALLRADIUS, y - BALLRADIUS,
1430 2*BALLRADIUS, 2*BALLRADIUS,
1431 (offset + 5760) % 23040, 5760);
1432 XFillArc(dpy, window, gc,
1433 x - BALLRADIUS, y - BALLRADIUS,
1434 2*BALLRADIUS, 2*BALLRADIUS,
1435 (offset + 17280) % 23040, 5760);
1436 } else { /* 180 degree divisions */
1437 XFillArc(dpy, window, gc,
1438 x - BALLRADIUS, y - BALLRADIUS,
1439 2*BALLRADIUS, 2*BALLRADIUS,
1441 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1442 XFillArc(dpy, window, gc,
1443 x - BALLRADIUS, y - BALLRADIUS,
1444 2*BALLRADIUS, 2*BALLRADIUS,
1445 (offset + 11520) % 23040, 11520);
1451 draw_juggle(ModeInfo * mi)
1453 Display *dpy = MI_DISPLAY(mi);
1454 Window window = MI_WINDOW(mi);
1461 if (juggles == NULL)
1463 sp = &juggles[MI_SCREEN(mi)];
1464 if (sp->trace == NULL)
1467 MI_IS_DRAWN(mi) = True;
1473 # ifdef GETTIMEOFDAY_TWO_ARGS
1474 struct timezone tzp;
1475 gettimeofday(&tv, &tzp);
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 */