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", ".juggle.pattern", XrmoptionSepArg, NULL},
137 {(char* ) "-trail", ".juggle.trail", XrmoptionSepArg, NULL},
139 {"-uni", ".juggle.uni", XrmoptionNoArg, "on"},
140 {"+uni", ".juggle.uni", XrmoptionNoArg, "off"},
142 {"-solid", ".juggle.solid", XrmoptionNoArg, "on"},
143 {"+solid", ".juggle.solid", XrmoptionNoArg, "off"}
145 static argtype vars[] =
147 {&pattern, "pattern",
148 "Pattern", (char *) DEF_PATTERN, t_String},
149 {&trail, "trail", "Trail", DEF_TRAIL, t_Int},
151 {&uni, "uni", "Uni", DEF_UNI, t_Bool},
153 {&solid, "solid", "Solid", DEF_SOLID, t_Bool}
155 static OptionStruct desc[] =
157 {"-pattern string", "Cambridge Juggling Pattern"},
158 {"-trail num", "Trace Juggling Patterns"},
160 {"-/+uni", "Unicycle"},
162 {"-/+solid", "solid color (else its a 4 panel look (half white))"}
165 ModeSpecOpt juggle_opts =
166 {sizeof opts / sizeof opts[0], opts,
167 sizeof vars / sizeof vars[0], vars, desc};
170 ModStruct juggle_description = {
171 "juggle", "init_juggle", "draw_juggle", "release_juggle",
172 "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts,
173 10000, 150, 30, 1, 64, 1.0, "",
174 "Shows a Juggler, juggling", 0, NULL
180 #include <X11/unix_time.h>
184 #include <sys/time.h>
186 #if HAVE_SYS_SELECT_H
187 #include <sys/select.h>
192 #define ARMLENGTH ((int) (40.0 * sp->scale))
193 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
194 #define POSE ((int) (10.0 * sp->scale))
195 #define SX ((int) (25.0 * sp->scale))
196 #define SZ ((int) (25.0 * sp->scale))
197 #define SY ((int) (25.0 * sp->scale))
198 #define HIPY ((int) (85.0 * sp->scale))
199 #define RHIPX ((int) (-15.0 * sp->scale))
200 #define LHIPX ((int) (15.0 * sp->scale))
201 #define RFX ((int) (-25.0 * sp->scale))
202 #define LFX ((int) (25.0 * sp->scale))
203 #define FY ((int) (155.0 * sp->scale))
204 #define WSTY ((int) (65.0 * sp->scale))
205 #define NEY ((int) (15.0 * sp->scale))
206 #define HED ((int) (35.0 * sp->scale))
207 #define BALLRADIUS ARMWIDTH
210 #define TRACE_LENGTH 50
211 #define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */
216 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
219 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
221 #define THROW_CATCH_INTERVAL (sp->count)
222 #define THROW_NULL_INTERVAL (sp->count * 0.5)
223 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
224 #define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */
229 typedef enum {HEIGHT, ADAM} Notation;
230 typedef enum {Empty, Full, Ball} Throwable;
231 typedef enum {LEFT, RIGHT} Hand;
232 typedef enum {THROW, CATCH} Action; /* DROP is not an option */
233 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR,
234 PREDICTOR} TrajectoryStatus;
236 typedef struct trajectory *TrajectoryPtr;
238 typedef struct {double a, b, c, d; } Spline;
240 typedef struct trajectory {
241 TrajectoryPtr prev, next; /* for building list */
242 TrajectoryStatus status;
256 double degree_offset;
257 TrajectoryPtr balllink;
258 TrajectoryPtr handlink;
262 double dx; /* initial velocity */
269 int x, y; /* current position */
284 XPoint figure_path[FIGURE1];
285 XSegment figure_segs[FIGURE2];
290 time_t begintime; /* seconds */
291 int time; /* millisecond timer */
295 static jugglestruct *juggles = (jugglestruct *) NULL;
310 static PatternIndex* patternindex = (PatternIndex *) NULL;
312 /* List of popular patterns, in any order */
313 static patternstruct portfolio[] = {
314 {"[+2 1]", "+3 1, Typical 2 ball juggler"},
315 {"[2 0]", "4 0, 2 balls 1 hand"},
316 {"[2 0 1]", "5 0 1"},
317 {"[+2 0 +2 0 0]", "+5 0 +5 0 0"},
318 {"[3]", "3, cascade"},
319 {"[+3]", "+3, reverse cascade"},
320 {"[=3]", "=3, cascade under arm"},
321 {"[&3]", "&3, cascade catching under arm"},
322 {"[_3]", "_3, bouncing cascade"},
323 {"[+3 x3 =3]", "+3 x3 =3, Mill's mess"},
324 {"[3 2 1]", "5 3 1"},
325 {"[3 3 1]", "4 4 1"},
326 {"[3 1 2]", "6 1 2, See-saw"},
327 {"[=3 3 1 2]", "=4 5 1 2"},
328 {"[=3 2 2 3 1 2]", "=6 2 2 5 1 2, =4 5 1 2 stretched"},
329 {"[+3 3 1 3]", "+4 4 1 3, anemic shower box"},
330 {"[3 3 1]", "4 4 1"},
331 {"[+3 2 3]", "+4 2 3"},
332 {"[+3 1]", "+5 1, 3 shower"},
333 {"[_3 1]", "_5 1, bouncing 3 shower"},
334 {"[3 0 3 0 3]", "5 0 5 0 5, shake 3 out of 5"},
335 {"[3 3 3 0 0]", "5 5 5 0 0, flash 3 out of 5"},
336 {"[3 3 0]", "4 5 0, complete waste of a 5 ball juggler"},
337 {"[3 3 3 0 0 0 0]", "7 7 7 0 0 0 0, 3 flash"},
338 {"[+3 0 +3 0 +3 0 0]", "+7 0 +7 0 +7 0 0"},
339 {"[4]", "4, 4 cascade"},
340 {"[+4 3]", "+5 3, 4 ball half shower"},
341 {"[4 4 2]", "5 5 2"},
342 {"[+4 4 4 +4]", "+4 4 4 +4, 4 columns"},
343 {"[4 3 +4]", "5 3 +4"},
344 {"[+4 1]", "+7 1, 4 shower"},
345 {"[4 4 4 4 0]", "5 5 5 5 0, learning 5"},
346 {"[5]", "5, 5 cascade"},
347 {"[_5 _5 _5 _5 _5 5 5 5 5 5]", "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"},
348 {"[+5 x5 =5]", "+5 x5 =5, Mill's mess for 5"},
349 {"[6]", "6, 6 cascade"},
350 {"[7]", "7, 7 cascade"},
351 {"[_7]", "_7, bouncing 7 cascade"},
354 /* Private Functions */
356 /* list management */
358 /* t receives trajectory to be created. ot must point to an existing
359 trajectory or be identical to t to start a new list. */
360 #define INSERT_AFTER_TOP(t, ot) \
361 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
362 {free_juggle(sp); return;} \
363 (t)->next = (ot)->next; \
366 (t)->next->prev = (t)
367 #define INSERT_AFTER(t, ot) \
368 if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \
369 {free_juggle(sp); return False;} \
370 (t)->next = (ot)->next; \
373 (t)->next->prev = (t)
376 /* t must point to an existing trajectory. t must not be an
377 expression ending ->next or ->prev */
379 (t)->next->prev = (t)->prev; \
380 (t)->prev->next = (t)->next; \
381 (void) free((void *) t)
384 free_pattern(jugglestruct *sp) {
385 if (sp->head != NULL) {
386 while (sp->head->next != sp->head) {
387 Trajectory *t = sp->head->next;
389 REMOVE(t); /* don't eliminate t */
391 (void) free((void *) sp->head);
392 sp->head = (Trajectory *) NULL;
397 free_juggle(jugglestruct *sp)
399 if (sp->trace != NULL) {
400 (void) free((void *) sp->trace);
401 sp->trace = (XPoint *) NULL;
407 add_throw(jugglestruct *sp, char type, int h, Notation n)
411 INSERT_AFTER(t, sp->head->prev);
423 /* add a Thratch to the performance */
425 program(ModeInfo *mi, const char *patn, int repeat)
427 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
433 if (MI_IS_VERBOSE(mi)) {
434 (void) fprintf(stderr, "%s x %d\n", patn, repeat);
437 for(i=0; i < repeat; i++) {
442 for(p=patn; *p; p++) {
443 if (*p >= '0' && *p <='9') {
445 h = 10*h + (*p - '0');
447 Notation nn = notation;
449 case '[': /* begin Adam notation */
452 case '-': /* Inside throw */
453 case '+': /* Outside throw */
454 case '=': /* Cross throw */
455 case '&': /* Cross catch */
456 case 'x': /* Cross throw and catch */
457 case '_': /* Bounce */
460 case '*': /* Lose ball */
464 case ']': /* end Adam notation */
469 if (!add_throw(sp, type, h, notation))
478 (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p);
484 if (!add_throw(sp, type, h, notation))
499 4 4 1 3 12 2 4 1 4 4 13 0 3 1
502 #define BOUNCEOVER 10
505 adam(jugglestruct *sp)
508 for(t = sp->head->next; t != sp->head; t = t->next) {
509 if (t->status == ATCH) {
512 for(p = t->next; a > 0 && p != sp->head; p = p->next) {
513 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
518 if(t->height > BOUNCEOVER && t->posn == ' '){
519 t->posn = '_'; /* high defaults can be bounced */
523 (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height);
529 /* Split Thratch notation into explicit throws and catches.
530 Usually Catch follows Throw in same hand, but take care of special
533 /* ..n1.. -> .. LTn RT1 LC RC .. */
534 /* ..nm.. -> .. LTn LC RTm RC .. */
537 part(jugglestruct *sp)
539 Trajectory *t, *nt, *p;
540 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
542 for (t = sp->head->next; t != sp->head; t = t->next) {
543 if (t->status > THRATCH) {
545 } else if (t->status == THRATCH) {
548 /* plausibility check */
549 if (t->height <= 2 && t->posn == '_') {
550 t->posn = ' '; /* no short bounces */
552 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
553 t->posn = ' '; /* 1's need close catches */
558 case ' ': /* fall through */
559 case '-': posn = '-'; t->posn = '+'; break;
560 case '+': posn = '+'; t->posn = '-'; break;
561 case '=': posn = '='; t->posn = '+'; break;
562 case '&': posn = '+'; t->posn = '='; break;
563 case 'x': posn = '='; t->posn = '='; break;
564 case '_': posn = '_'; t->posn = '-'; break;
565 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
567 hand = (Hand) ((hand + 1) % 2);
572 if (t->height == 1) {
573 p = p->prev; /* early throw */
579 nt->height = t->height;
587 /* Connnect up throws and catches to figure out which ball goes where.
588 Do the same with the juggler's hands. */
593 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
596 for (t = sp->head->next; t != sp->head; t = t->next) {
597 if (t->status == ACTION) {
599 (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n",
602 (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'),
605 if (t->action == THROW) {
606 if (t->type == Empty) {
607 if (MI_NPIXELS(mi) > 2) {
608 t->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
610 t->spin = NRAND(5) - 2;
611 t->degree_offset = NRAND(360);
612 t->divisions = 2 * ((LRAND() & 1) + 1);
615 /* search forward for next hand catch */
616 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
617 if (p->action == CATCH) {
618 if (t->handlink == NULL && p->hand == t->hand) {
619 t->handlink = p; /* next catch in this hand */
627 /* search forward for next ball catch */
628 for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) {
629 if (p->action == CATCH) {
630 if (t->balllink == NULL && --h < 1) { /* caught */
632 if (p->type == Full) {
636 t->balllink = p; /* complete trajectory */
638 p->color = t->color; /* accept catch */
640 p->degree_offset = t->degree_offset;
641 p->divisions = t->divisions;
646 t->type = Empty; /* thrown */
647 } else if (t->action == CATCH) {
648 /* search forward for next throw from this hand */
649 for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) {
650 if (p->action == THROW && p->hand == t->hand) {
651 p->type = t->type; /* pass ball */
652 p->color = t->color; /* pass color */
653 p->spin = NRAND(5) - 2;
654 p->degree_offset = NRAND(360);
655 p->divisions = 2 * ((LRAND() & 1) + 1);
660 t->status = LINKEDACTION;
665 /* Convert hand position symbols into actual time/space coordinates */
667 positions(jugglestruct *sp)
671 for (t = sp->head->next; t != sp->head; t = t->next) {
672 if (t->status == PTHRATCH) {
674 } else if (t->status == LINKEDACTION) {
678 if (t->action == CATCH) {
679 if (t->type == Empty) {
680 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
682 now += THROW_CATCH_INTERVAL; /* successful catch */
685 now += (int) CATCH_THROW_INTERVAL; /* throws are very short */
692 case '-': xo = SX - POSE; break;
694 case '+': xo = SX + POSE; break;
695 case '=': xo = - SX - POSE; yo += 2 * POSE; break;
696 default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break;
698 t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo);
701 t->status = PTHRATCH;
707 /* Private physics functions */
710 makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1)
719 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
720 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
725 s.c = (3*a*t0 - 2*b)*t0 + c;
726 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
731 makeSplinePair(Spline *s1, Spline *s2,
732 int x0, double dx0, int t0,
734 int x2, double dx2, int t2)
737 double t21, t10, t20, dx1;
743 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
744 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
746 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
747 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
751 /* Turn abstract timings into physically appropriate ball trajectories. */
753 projectile(jugglestruct *sp)
756 for (t = sp->head->next; t != sp->head; t = t->next) {
757 if (t->status != PTHRATCH) {
760 if (t->action == THROW) {
761 if (t->balllink != NULL) {
762 if (t->posn == '_') { /* Bounce once */
763 double tc, y0, yf, yc, tb, e, i;
765 tc = t->balllink->start - t->start;
770 e = 1; /* permissible error in yc */
774 yt = height at catch time after one bounce
775 one or three roots according to timing
776 find one by interval bisection
779 for(i = tc / 2; i > 0; i/=2){
782 (void) fprintf(stderr, "div by zero!\n");
785 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
787 yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf;
790 }else if(yt < yc - e){
800 t->dx = (t->balllink->x - t->x) / tc;
802 /* ball follows parabola down */
803 INSERT_AFTER(n, t->prev);
805 n->finish = (int) (t->start + tb);
809 n->degree_offset = t->degree_offset;
810 n->divisions = t->divisions;
811 n->status = PREDICTOR;
813 t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb;
815 /* Represent parabola as a degenerate spline -
816 linear in x, quadratic in y */
820 n->xp.d = -t->dx*t0 + t->x;
823 n->yp.c = -sp->Gr*t0 + t->dy;
824 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
827 /* ball follows parabola up */
828 INSERT_AFTER(n, t->prev);
829 n->start = (int) (t0 + tb);
830 n->finish = (int) (t0 + tc);
834 n->degree_offset = t->degree_offset;
835 n->divisions = t->divisions;
836 n->status = PREDICTOR;
841 n->xp.d = -t->dx*t0 + t->x;
843 dy = (yf - y0)/tb + 0.5*sp->Gr*tb;
845 /* Represent parabola as a degenerate spline -
846 linear in x, quadratic in y */
849 n->yp.c = -sp->Gr*t0 - COR*dy;
850 n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf;
853 t->status = BPREDICTOR;
858 /* ball follows parabola */
859 INSERT_AFTER(n, t->prev);
861 n->finish = t->balllink->start;
865 n->degree_offset = t->degree_offset;
866 n->divisions = t->divisions;
867 n->status = PREDICTOR;
870 dt = t->balllink->start - t->start;
871 t->dx = (t->balllink->x - t->x) / dt;
872 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
874 /* Represent parabola as a degenerate spline -
875 linear in x, quadratic in y */
879 n->xp.d = -t->dx*t0 + t->x;
882 n->yp.c = -sp->Gr*t0 + t->dy;
883 n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y;
886 t->status = BPREDICTOR;
888 } else { /* Zero Throw */
889 t->status = BPREDICTOR;
896 /* Turn abstract hand motions into cubic splines. */
898 hands(jugglestruct *sp)
900 Trajectory *t, *u, *v;
901 for (t = sp->head->next; t != sp->head; t = t->next) {
902 /* no throw => no velocity */
903 if (t->status != BPREDICTOR) {
908 if (u == NULL) { /* no next catch */
912 if (v == NULL) { /* no next throw */
916 /* double spline takes hand from throw, thru catch, to
919 t->finish = u->start;
920 t->status = PREDICTOR;
922 u->finish = v->start;
923 u->status = PREDICTOR;
925 (void) makeSplinePair(&t->xp, &u->xp,
926 t->x, t->dx, t->start,
928 v->x, v->dx, v->start);
929 (void) makeSplinePair(&t->yp, &u->yp,
930 t->y, t->dy, t->start,
932 v->y, v->dy, v->start);
934 t->status = PREDICTOR;
938 /* Given target x, y find_elbow puts hand at target if possible,
939 * otherwise makes hand point to the target */
941 find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z)
945 h2 = x*x + y*y + z*z;
946 if (h2 > 4*ARMLENGTH*ARMLENGTH) {
947 t = ARMLENGTH/sqrt(h2);
948 e->x = (short) (t*x);
949 e->y = (short) (t*y);
953 r = sqrt((double)(x*x + z*z));
954 t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1);
955 e->x = (short) (x*(1 - y*t/r)/2);
956 e->y = (short) ((y + r*t)/2);
962 /* NOTE: returned x, y adjusted for arm reach */
964 draw_arm(ModeInfo * mi, Hand side, int *x, int *y)
966 Display *dpy = MI_DISPLAY(mi);
967 Window win = MI_WINDOW(mi);
969 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
971 int sig = (side == LEFT) ? 1 : -1;
973 XSetLineAttributes(dpy, gc,
974 ARMWIDTH, LineSolid, CapRound, JoinRound);
975 if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) {
977 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi));
978 find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ);
979 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
980 *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x;
981 *y = sp->arm[side][0].y = sp->cy + SY + h.y;
982 sp->arm[side][1].x = sp->cx + sig*SX + e.x;
983 sp->arm[side][1].y = sp->cy + SY + e.y;
985 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
986 XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin);
987 XSetLineAttributes(dpy, gc,
988 1, LineSolid, CapNotLast, JoinRound);
992 draw_figure(ModeInfo * mi)
994 Display *dpy = MI_DISPLAY(mi);
995 Window win = MI_WINDOW(mi);
997 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
999 XSetLineAttributes(dpy, gc,
1000 ARMWIDTH, LineSolid, CapRound, JoinRound);
1001 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1002 XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin);
1003 XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2);
1004 XDrawArc(dpy, win, gc,
1005 sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360);
1006 XSetLineAttributes(dpy, gc,
1007 1, LineSolid, CapNotLast, JoinRound);
1011 /* dumps a human-readable rendition of the current state of the juggle
1012 pipeline to stderr for debugging */
1015 dump(jugglestruct *sp)
1019 for (t = sp->head->next; t != sp->head; t = t->next) {
1020 switch (t->status) {
1022 (void) fprintf(stderr, "T %c%d\n", t->posn, t->height);
1025 (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n",
1027 t->hand ? 'R' : 'L',
1028 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1032 (void) fprintf(stderr, "L %c%c%c%d %d\n",
1035 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1036 t->height, t->color);
1039 (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn,
1041 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1042 t->height, t->type, t->color,
1043 t->start, t->finish);
1046 (void) fprintf(stderr, "P %c %2d %6d %6d %g\n",
1047 t->type == Ball?'b':t->type == Empty?'e':'f',
1049 t->start, t->finish, t->yp.c);
1052 (void) fprintf(stderr, "status %d not implemented\n", t->status);
1059 static int get_num_balls(const char *j)
1064 for (p = j; *p; p++) {
1065 if (*p >= '0' && *p <='9') { /* digit */
1066 h = 10*h + (*p - '0');
1081 static int compare_num_balls(const void *p1, const void *p2)
1083 int i = get_num_balls(((patternstruct*)p1)->pattern);
1084 int j = get_num_balls(((patternstruct*)p2)->pattern);
1098 /* Public functions */
1101 release_juggle(ModeInfo * mi)
1103 if (juggles != NULL) {
1106 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
1107 free_juggle(&juggles[screen]);
1108 (void) free((void *) juggles);
1109 juggles = (jugglestruct *) NULL;
1111 if (patternindex != NULL) {
1112 (void) free((void *) patternindex);
1113 patternindex = (PatternIndex *) NULL;
1118 init_juggle(ModeInfo * mi)
1122 XPoint figure1[FIGURE1];
1123 XSegment figure2[FIGURE2];
1124 if (pattern != NULL && *pattern == '.') {
1127 if (pattern == NULL && patternindex == NULL) {
1128 /* pattern list needs indexing */
1129 int nelements = sizeof(portfolio)/sizeof(patternstruct);
1133 /* sort according to number of balls */
1134 qsort((void*)portfolio, nelements,
1135 sizeof(patternstruct), compare_num_balls);
1137 /* last pattern has most balls */
1138 maxballs = get_num_balls(portfolio[nelements - 1].pattern);
1139 /* allocate index */
1140 if ((patternindex = (PatternIndex *) calloc(maxballs + 1,
1141 sizeof (PatternIndex))) == NULL) {
1145 /* run through sorted list, indexing start of each group
1146 and number in group */
1148 for (i = 0; i < nelements; i++) {
1149 int b = get_num_balls(portfolio[i].pattern);
1151 if (MI_IS_VERBOSE(mi)) {
1152 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1153 numpat, maxballs, (numpat == 1) ? "" : "s");
1155 patternindex[maxballs].number = numpat;
1158 patternindex[maxballs].start = i;
1163 if (MI_IS_VERBOSE(mi)) {
1164 (void) fprintf(stderr, "%d %d ball pattern%s\n",
1165 numpat, maxballs, (numpat == 1) ? "" : "s");
1167 patternindex[maxballs].number = numpat;
1170 if (juggles == NULL) { /* allocate jugglestruct */
1171 if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi),
1172 sizeof (jugglestruct))) == NULL) {
1177 sp = &juggles[MI_SCREEN(mi)];
1181 if (MI_IS_FULLRANDOM(mi)) {
1182 sp->solid = (Bool) (LRAND() & 1);
1184 sp->uni = (Bool) (LRAND() & 1);
1193 sp->width = MI_WIDTH(mi);
1194 sp->height = MI_HEIGHT(mi);
1195 sp->count = ABS(MI_COUNT(mi));
1198 sp->scale = sp->height / 480.0;
1199 /* vary x a little so the juggler does not burn the screen */
1200 sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1);
1201 sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */
1202 /* "7" hits top of screen */
1203 sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL);
1205 figure1[0].x = LHIPX, figure1[0].y = HIPY;
1206 figure1[1].x = 0, figure1[1].y = WSTY;
1207 figure1[2].x = SX, figure1[2].y = SY;
1208 figure1[3].x = -SX, figure1[3].y = SY;
1209 figure1[4].x = 0, figure1[4].y = WSTY;
1210 figure1[5].x = RHIPX, figure1[5].y = HIPY;
1211 figure1[6].x = LHIPX, figure1[6].y = HIPY;
1212 figure2[0].x1 = 0, figure2[0].y1 = SY,
1213 figure2[0].x2 = 0, figure2[0].y2 = NEY;
1214 figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY,
1215 figure2[1].x2 = LFX, figure2[1].y2 = FY;
1216 figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY,
1217 figure2[2].x2 = RFX, figure2[2].y2 = FY;
1220 for (i = 0; i < FIGURE1; i++) {
1221 sp->figure_path[i].x = figure1[i].x + sp->cx;
1222 sp->figure_path[i].y = figure1[i].y + sp->cy;
1225 for (i = 0; i < FIGURE2; i++) {
1226 sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx;
1227 sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy;
1228 sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx;
1229 sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy;
1232 sp->arm[LEFT][2].x = sp->cx + SX;
1233 sp->arm[LEFT][2].y = sp->cy + SY;
1234 sp->arm[RIGHT][2].x = sp->cx - SX;
1235 sp->arm[RIGHT][2].y = sp->cy + SY;
1237 if (sp->trace == NULL) {
1238 if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) {
1244 /* Clear the background. */
1249 /* record start time */
1250 sp->begintime = time(NULL);
1254 /* create circular list */
1255 INSERT_AFTER_TOP(sp->head, sp->head);
1257 /* generate pattern */
1258 if (pattern == NULL) {
1261 #define MAXREPEAT 30
1262 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
1263 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
1266 int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS);
1267 while (count < MI_CYCLES(mi)) {
1268 char buf[MAXPAT * 3 + 3], *b = buf;
1270 int l = NRAND(MAXPAT) + 1;
1271 int t = NRAND(MAXREPEAT) + 1;
1273 { /* vary number of balls */
1274 int new_balls = num_balls;
1277 if (new_balls == 2) /* Do not juggle 2 that often */
1278 change = NRAND(2 + CHANGE_BIAS / 4);
1280 change = NRAND(2 + CHANGE_BIAS);
1291 if (new_balls < MINBALLS) {
1294 if (new_balls > MAXBALLS) {
1297 if (new_balls < num_balls) {
1298 if (!program(mi, "[*]", 1)) /* lose ball */
1301 num_balls = new_balls;
1305 if (NRAND(2) && patternindex[num_balls].number) {
1306 /* Pick from PortFolio */
1308 portfolio[patternindex[num_balls].start +
1309 NRAND(patternindex[num_balls].number)].pattern,
1313 /* Invent a new pattern */
1315 for(i = 0; i < l; i++){
1317 do { /* Triangular Distribution => high values more likely */
1318 m = NRAND(num_balls + 1);
1319 n = NRAND(num_balls + 1);
1321 if (n == num_balls) {
1324 switch(NRAND(6 + POSITION_BIAS)){
1325 case 0: /* Inside throw */
1327 case 1: /* Outside throw */
1329 case 2: /* Cross throw */
1331 case 3: /* Cross catch */
1333 case 4: /* Cross throw and catch */
1335 case 5: /* Bounce */
1347 if (!program(mi, buf, t))
1352 } else { /* pattern supplied in height or 'a' notation */
1353 if (!program(mi, pattern, MI_CYCLES(mi)))
1366 if (!projectile(sp))
1376 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1380 * Workaround SunOS 4 framebuffer bug which causes balls to leave dust
1381 * trace behind when erased
1383 #define ERASE_BALL(x,y) \
1384 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1385 XFillArc(dpy, window, gc, \
1386 (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \
1387 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040)
1390 #define ERASE_BALL(x,y) \
1391 XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \
1392 XFillArc(dpy, window, gc, \
1393 (x) - BALLRADIUS, (y) - BALLRADIUS, \
1394 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040)
1398 draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions)
1400 Display *dpy = MI_DISPLAY(mi);
1401 Window window = MI_WINDOW(mi);
1403 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1406 XSetForeground(dpy, gc, color);
1407 if ((color == MI_WHITE_PIXEL(mi)) ||
1408 ((divisions != 2) && (divisions != 4)) || sp->solid) {
1409 XFillArc(dpy, window, gc,
1410 x - BALLRADIUS, y - BALLRADIUS,
1411 2*BALLRADIUS, 2*BALLRADIUS,
1415 offset = (int) (degree_offset * 64);
1416 if (divisions == 4) { /* 90 degree divisions */
1417 XFillArc(dpy, window, gc,
1418 x - BALLRADIUS, y - BALLRADIUS,
1419 2*BALLRADIUS, 2*BALLRADIUS,
1421 XFillArc(dpy, window, gc,
1422 x - BALLRADIUS, y - BALLRADIUS,
1423 2*BALLRADIUS, 2*BALLRADIUS,
1424 (offset + 11520) % 23040, 5760);
1425 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1426 XFillArc(dpy, window, gc,
1427 x - BALLRADIUS, y - BALLRADIUS,
1428 2*BALLRADIUS, 2*BALLRADIUS,
1429 (offset + 5760) % 23040, 5760);
1430 XFillArc(dpy, window, gc,
1431 x - BALLRADIUS, y - BALLRADIUS,
1432 2*BALLRADIUS, 2*BALLRADIUS,
1433 (offset + 17280) % 23040, 5760);
1434 } else { /* 180 degree divisions */
1435 XFillArc(dpy, window, gc,
1436 x - BALLRADIUS, y - BALLRADIUS,
1437 2*BALLRADIUS, 2*BALLRADIUS,
1439 XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi));
1440 XFillArc(dpy, window, gc,
1441 x - BALLRADIUS, y - BALLRADIUS,
1442 2*BALLRADIUS, 2*BALLRADIUS,
1443 (offset + 11520) % 23040, 11520);
1449 draw_juggle(ModeInfo * mi)
1451 Display *dpy = MI_DISPLAY(mi);
1452 Window window = MI_WINDOW(mi);
1459 if (juggles == NULL)
1461 sp = &juggles[MI_SCREEN(mi)];
1462 if (sp->trace == NULL)
1465 MI_IS_DRAWN(mi) = True;
1471 # ifdef GETTIMEOFDAY_TWO_ARGS
1472 struct timezone tzp;
1473 gettimeofday(&tv, &tzp);
1478 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
1480 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
1482 if (traj->status != PREDICTOR) {
1485 if (traj->start > future) {
1486 future = traj->start;
1488 if (sp->time < traj->start) { /* early */
1490 } else if (sp->time < traj->finish) { /* working */
1491 int x = (int) CUBIC(traj->xp, sp->time);
1492 int y = (int) CUBIC(traj->yp, sp->time);
1493 unsigned long color;
1495 if (MI_NPIXELS(mi) > 2) {
1496 color = MI_PIXEL(mi, traj->color);
1498 color = MI_WHITE_PIXEL(mi);
1500 if (traj->type == Empty || traj->type == Full) {
1501 draw_arm(mi, traj->hand, &x, &y);
1503 if (traj->type == Ball || traj->type == Full) {
1505 ERASE_BALL(sp->trace[sp->traceindex].x,
1506 sp->trace[sp->traceindex].y);
1507 sp->trace[sp->traceindex].x = traj->x;
1508 sp->trace[sp->traceindex].y = traj->y;
1509 if (++sp->traceindex >= trail) {
1513 ERASE_BALL(traj->x, traj->y);
1515 draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions);
1516 traj->degree_offset = traj->degree_offset +
1517 SPIN_DEGREES * traj->spin / sp->count;
1518 if (traj->degree_offset < 0.0)
1519 traj->degree_offset += 360.0;
1520 else if (traj->degree_offset >= 360.0)
1521 traj->degree_offset -= 360.0;
1525 } else { /* expired */
1526 Trajectory *n = traj;
1528 ERASE_BALL(traj->x, traj->y);
1534 /*** FIXME-BEGIN ***/
1535 /* pattern generator would refill here when necessary */
1539 if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */
1547 #endif /* MODE_juggle */