X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Fjuggle.c;fp=hacks%2Fjuggle.c;h=758d2576f145e37a3e002ea5b72b94ac61c87587;hb=a94197e76a5dea5cb60542840809d6c20d0abbf3;hp=0000000000000000000000000000000000000000;hpb=8eb2873d7054e705c4e83f22d18c40946a9e2529;p=xscreensaver diff --git a/hacks/juggle.c b/hacks/juggle.c new file mode 100644 index 00000000..758d2576 --- /dev/null +++ b/hacks/juggle.c @@ -0,0 +1,1549 @@ +/* -*- Mode: C; tab-width: 4 -*- */ +/* juggle */ + +#if !defined( lint ) && !defined( SABER ) +static const char sccsid[] = "@(#)juggle.c 5.00 2000/11/01 xlockmore"; + +#endif + +/*- + * Copyright (c) 1996 by Tim Auckland + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation. + * + * This file is provided AS IS with no warranties of any kind. The author + * shall have no liability with respect to the infringement of copyrights, + * trade secrets or any patents by this file or any part thereof. In no + * event will the author be liable for any lost revenue or profits or + * other special, indirect and consequential damages. + * + * Revision History + * 01-Nov-2000: Allocation checks + * 1996: Written + */ + +/*- + * TODO + * Fix timing to run at approx same speed on all machines. + * Store shorter pattern and refill when required. + * Use -cycles and -count in a rational manner. + * Merge pattern selector with pattern generator. + * Add clubs + * Clap when all the balls are in the air + */ + + +/*- +Notes on Adam Chalcraft Juggling Notation (used by permission) +a-> Adam's notation s-> Site swap (Cambridge) notation + +To define a map from a-notation to s-notation +("site-swap"), both of which look like doubly infinite sequences of natural +numbers. In s-notation, there is a restriction on what is allowed, namely +for the sequence s_n, the associated function f(n)=n+s_n must be a +bijection. In a-notation, there is no restriction. + +To go from a-notation to s-notation, you start by mapping each a_n to a +permutation of N, the natural numbers. + +0 -> the identity +1 -> (10) [i.e. f(1)=0, f(0)=1] +2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2] +3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3] +etc. + +Then for each n, you look at how long 0 takes to get back to 0 again and +you call this t_n. If a_n=0, for example, then since the identity leaves 0 +alone, it gets back to 0 in 1 step, so t_n=1. If a_n=1, then f(0)=1. Now any +further a_n=0 leave 1 alone, but the next a_n>0 sends 1 back to 0. Hence t_n +is 2 + the number of 0's following the 1. Finally, set s_n = t_n - 1. + +To give some examples, it helps to have a notation for cyclic sequences. By +(123), for example, I mean ...123123123123... . Now under the a-notation -> +s-notation mapping we have some familiar examples: + +(0)->(0), (1)->(1), (2)->(2) etc. +(21)->(31), (31)->(51), (41)->(71) etc. +(10)->(20), (20)->(40), (30)->(60) etc. +(331)->(441), (312)->(612), (303)->(504), (321)->(531) +(43)->(53), (434)->(534), (433)->(633) +(552)->(672) + +In general, the number of balls is the *average* of the s-notation, and the +*maximum* of the a-notation. Another theorem is that the minimum values in +the a-notation and the s-notation and equal, and preserved in the same +positions. + +The usefulness of a-notation is the fact that there are no restrictions on +what is allowed. This makes random juggle generation much easier. It also +makes enumeration very easy. Another handy feature is computing changes. +Suppose you can do (5) and want a neat change up to (771) in s-notation +[Mike Day actually needed this example!]. Write them both in a-notation, +which gives (5) and (551). Now concatenate them (in general, there may be +more than one way to do this, but not in this example), to get +...55555555551551551551551... +Now convert back to s-notation, to get +...55555566771771771771771... +So the answer is to do two 6 throws and then go straight into (771). +Coming back down of course, +...5515515515515515555555555... +converts to +...7717717717716615555555555... +so the answer is to do a single 661 and then drop straight down to (5). + +[The number of balls in the generated pattern occasionally changes. In + order to decrease the number of balls I had to introduce a new symbol + into the Adam notation, [*] which means 'lose the current ball'.] +*/ + +#define STANDALONE +#ifdef STANDALONE +#define MODE_juggle +#define PROGCLASS "Juggle" +#define HACK_INIT init_juggle +#define HACK_DRAW draw_juggle +#define juggle_opts xlockmore_opts +#define DEFAULTS "*delay: 10000 \n" \ +"*count: 150 \n" \ +"*cycles: 30 \n" \ +"*ncolors: 32 \n" +#define SMOOTH_COLORS +#include "xlockmore.h" /* in xscreensaver distribution */ +#else /* STANDALONE */ +#include "xlock.h" /* in xlockmore distribution */ +#endif /* STANDALONE */ + +#ifdef MODE_juggle + +#define DEF_PATTERN "." /* All patterns */ +#define DEF_TRAIL "0" /* No trace */ +#ifdef UNI +#define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */ +#endif +#define DEF_SOLID "FALSE" /* Not solid */ + +static char *pattern; +static int trail; +#ifdef UNI +static Bool uni; +#endif +static Bool solid; + +static XrmOptionDescRec opts[] = +{ + {(char* ) "-pattern", (char *) ".juggle.pattern", + XrmoptionSepArg, (caddr_t) NULL}, + {(char* ) "-trail", (char *) ".juggle.trail", + XrmoptionSepArg, (caddr_t) NULL}, +#ifdef UNI + {(char *) "-uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "on"}, + {(char *) "+uni", (char *) ".juggle.uni", XrmoptionNoArg, (caddr_t) "off"}, +#endif + {(char *) "-solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "on"}, + {(char *) "+solid", (char *) ".juggle.solid", XrmoptionNoArg, (caddr_t) "off"} +}; +static argtype vars[] = +{ + {(caddr_t *) &pattern, (char *) "pattern", + (char *) "Pattern", (char *) DEF_PATTERN, t_String}, + {(caddr_t *) &trail, (char *) "trail", + (char *) "Trail", (char *) DEF_TRAIL, t_Int}, +#ifdef UNI + {(caddr_t *) &uni, (char *) "uni", + (char *) "Uni", (char *) DEF_UNI, t_Bool}, +#endif + {(caddr_t *) &solid, (char *) "solid", + (char *) "Solid", (char *) DEF_SOLID, t_Bool} +}; +static OptionStruct desc[] = +{ + {(char *) "-pattern string", (char *) "Cambridge Juggling Pattern"}, + {(char *) "-trail num", (char *) "Trace Juggling Patterns"}, +#ifdef UNI + {(char *) "-/+uni", (char *) "Unicycle"}, +#endif + {(char *) "-/+solid", (char *) "solid color (else its a 4 panel look (half white))"} +}; + +ModeSpecOpt juggle_opts = +{sizeof opts / sizeof opts[0], opts, + sizeof vars / sizeof vars[0], vars, desc}; + +#ifdef USE_MODULES +ModStruct juggle_description = { + "juggle", "init_juggle", "draw_juggle", "release_juggle", + "draw_juggle", "init_juggle", (char *) NULL, &juggle_opts, + 10000, 150, 30, 1, 64, 1.0, "", + "Shows a Juggler, juggling", 0, NULL +}; + +#endif + +#ifdef USE_XVMSUTILS +#include +#endif +#include +#if HAVE_SYS_TIME_H +#include +#else +#if HAVE_SYS_SELECT_H +#include +#endif +#endif + +/* Figure */ +#define ARMLENGTH ((int) (40.0 * sp->scale)) +#define ARMWIDTH ((int) (8.0 * sqrt(sp->scale))) +#define POSE ((int) (10.0 * sp->scale)) +#define SX ((int) (25.0 * sp->scale)) +#define SZ ((int) (25.0 * sp->scale)) +#define SY ((int) (25.0 * sp->scale)) +#define HIPY ((int) (85.0 * sp->scale)) +#define RHIPX ((int) (-15.0 * sp->scale)) +#define LHIPX ((int) (15.0 * sp->scale)) +#define RFX ((int) (-25.0 * sp->scale)) +#define LFX ((int) (25.0 * sp->scale)) +#define FY ((int) (155.0 * sp->scale)) +#define WSTY ((int) (65.0 * sp->scale)) +#define NEY ((int) (15.0 * sp->scale)) +#define HED ((int) (35.0 * sp->scale)) +#define BALLRADIUS ARMWIDTH +#define FIGURE1 7 +#define FIGURE2 3 +#define TRACE_LENGTH 50 +#define SPIN_DEGREES 750 /* Average spinning between a throw and the next catch */ + +/* macros */ + +#ifndef XtNumber +#define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0]))) +#endif + +#define GRAVITY(h, t) 4*(double)(h)/((t)*(t)) + +#define THROW_CATCH_INTERVAL (sp->count) +#define THROW_NULL_INTERVAL (sp->count * 0.5) +#define CATCH_THROW_INTERVAL (sp->count * 0.2) +#define COR 0.8 /* coeff of restitution of balls (1 = perfect bounce) */ + + +/* typedefs */ + +typedef enum {HEIGHT, ADAM} Notation; +typedef enum {Empty, Full, Ball} Throwable; +typedef enum {LEFT, RIGHT} Hand; +typedef enum {THROW, CATCH} Action; /* DROP is not an option */ +typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, PTHRATCH, BPREDICTOR, + PREDICTOR} TrajectoryStatus; + +typedef struct trajectory *TrajectoryPtr; + +typedef struct {double a, b, c, d; } Spline; + +typedef struct trajectory { + TrajectoryPtr prev, next; /* for building list */ + TrajectoryStatus status; + + /* Throw */ + char posn; + int height; + int adam; + + /* Action */ + Hand hand; + Action action; + + /* LinkedAction */ + int color; + int spin, divisions; + double degree_offset; + TrajectoryPtr balllink; + TrajectoryPtr handlink; + + /* PThratch */ + + double dx; /* initial velocity */ + double dy; + + /* Predictor */ + Throwable type; + int start, finish; + Spline xp, yp; + int x, y; /* current position */ +} Trajectory; + +/* structs */ + +typedef struct { + int width; + int height; + double scale; + int complexity; + int cx; + int cy; + double Gr; + int pattern; + Trajectory *head; + XPoint figure_path[FIGURE1]; + XSegment figure_segs[FIGURE2]; + XPoint arm[2][3]; + XPoint *trace; + int traceindex; + int count; + time_t begintime; /* seconds */ + int time; /* millisecond timer */ + Bool solid, uni; +} jugglestruct; + +static jugglestruct *juggles = (jugglestruct *) NULL; + +typedef struct { + char * pattern; + char * name; +} patternstruct; + +#define MINBALLS 2 +#define MAXBALLS 7 + +typedef struct { + int start; + int number; +} PatternIndex; + +static PatternIndex* patternindex = (PatternIndex *) NULL; + +/* List of popular patterns, in any order */ +static patternstruct portfolio[] = { + {(char *) "[+2 1]", (char *) "+3 1, Typical 2 ball juggler"}, + {(char *) "[2 0]", (char *) "4 0, 2 balls 1 hand"}, + {(char *) "[2 0 1]", (char *) "5 0 1"}, + {(char *) "[+2 0 +2 0 0]", (char *) "+5 0 +5 0 0"}, + {(char *) "[3]", (char *) "3, cascade"}, + {(char *) "[+3]", (char *) "+3, reverse cascade"}, + {(char *) "[=3]", (char *) "=3, cascade under arm"}, + {(char *) "[&3]", (char *) "&3, cascade catching under arm"}, + {(char *) "[_3]", (char *) "_3, bouncing cascade"}, + {(char *) "[+3 x3 =3]", (char *) "+3 x3 =3, Mill's mess"}, + {(char *) "[3 2 1]", (char *) "5 3 1"}, + {(char *) "[3 3 1]", (char *) "4 4 1"}, + {(char *) "[3 1 2]", (char *) "6 1 2, See-saw"}, + {(char *) "[=3 3 1 2]", (char *) "=4 5 1 2"}, + {(char *) "[=3 2 2 3 1 2]", (char *) "=6 2 2 5 1 2, =4 5 1 2 stretched"}, + {(char *) "[+3 3 1 3]", (char *) "+4 4 1 3, anemic shower box"}, + {(char *) "[3 3 1]", (char *) "4 4 1"}, + {(char *) "[+3 2 3]", (char *) "+4 2 3"}, + {(char *) "[+3 1]", (char *) "+5 1, 3 shower"}, + {(char *) "[_3 1]", (char *) "_5 1, bouncing 3 shower"}, + {(char *) "[3 0 3 0 3]", (char *) "5 0 5 0 5, shake 3 out of 5"}, + {(char *) "[3 3 3 0 0]", (char *) "5 5 5 0 0, flash 3 out of 5"}, + {(char *) "[3 3 0]", (char *) "4 5 0, complete waste of a 5 ball juggler"}, + {(char *) "[3 3 3 0 0 0 0]", (char *) "7 7 7 0 0 0 0, 3 flash"}, + {(char *) "[+3 0 +3 0 +3 0 0]", (char *) "+7 0 +7 0 +7 0 0"}, + {(char *) "[4]", (char *) "4, 4 cascade"}, + {(char *) "[+4 3]", (char *) "+5 3, 4 ball half shower"}, + {(char *) "[4 4 2]", (char *) "5 5 2"}, + {(char *) "[+4 4 4 +4]", (char *) "+4 4 4 +4, 4 columns"}, + {(char *) "[4 3 +4]", (char *) "5 3 +4"}, + {(char *) "[+4 1]", (char *) "+7 1, 4 shower"}, + {(char *) "[4 4 4 4 0]", (char *) "5 5 5 5 0, learning 5"}, + {(char *) "[5]", (char *) "5, 5 cascade"}, + {(char *) "[_5 _5 _5 _5 _5 5 5 5 5 5]", (char *) "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"}, + {(char *) "[+5 x5 =5]", (char *) "+5 x5 =5, Mill's mess for 5"}, + {(char *) "[6]", (char *) "6, 6 cascade"}, + {(char *) "[7]", (char *) "7, 7 cascade"}, + {(char *) "[_7]", (char *) "_7, bouncing 7 cascade"}, +}; + +/* Private Functions */ + +/* list management */ + +/* t receives trajectory to be created. ot must point to an existing + trajectory or be identical to t to start a new list. */ +#define INSERT_AFTER_TOP(t, ot) \ + if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \ + {free_juggle(sp); return;} \ + (t)->next = (ot)->next; \ + (t)->prev = (ot); \ + (ot)->next = (t); \ + (t)->next->prev = (t) +#define INSERT_AFTER(t, ot) \ + if ((t = (Trajectory *)calloc(1, sizeof(Trajectory))) == NULL) \ + {free_juggle(sp); return False;} \ + (t)->next = (ot)->next; \ + (t)->prev = (ot); \ + (ot)->next = (t); \ + (t)->next->prev = (t) + + +/* t must point to an existing trajectory. t must not be an + expression ending ->next or ->prev */ +#define REMOVE(t) \ + (t)->next->prev = (t)->prev; \ + (t)->prev->next = (t)->next; \ + (void) free((void *) t) + +static void +free_pattern(jugglestruct *sp) { + if (sp->head != NULL) { + while (sp->head->next != sp->head) { + Trajectory *t = sp->head->next; + + REMOVE(t); /* don't eliminate t */ + } + (void) free((void *) sp->head); + sp->head = (Trajectory *) NULL; + } +} + +static void +free_juggle(jugglestruct *sp) +{ + if (sp->trace != NULL) { + (void) free((void *) sp->trace); + sp->trace = (XPoint *) NULL; + } + free_pattern(sp); +} + +static Bool +add_throw(jugglestruct *sp, char type, int h, Notation n) +{ + Trajectory *t; + + INSERT_AFTER(t, sp->head->prev); + t->posn = type; + if (n == ADAM) { + t->adam = h; + t->status = ATCH; + } else { + t->height = h; + t->status = THRATCH; + } + return True; +} + +/* add a Thratch to the performance */ +static Bool +program(ModeInfo *mi, const char *patn, int repeat) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + const char *p; + int h, i, seen; + Notation notation; + char type; + + if (MI_IS_VERBOSE(mi)) { + (void) fprintf(stderr, "%s x %d\n", patn, repeat); + } + + for(i=0; i < repeat; i++) { + type=' '; + h = 0; + seen = 0; + notation = HEIGHT; + for(p=patn; *p; p++) { + if (*p >= '0' && *p <='9') { + seen = 1; + h = 10*h + (*p - '0'); + } else { + Notation nn = notation; + switch (*p) { + case '[': /* begin Adam notation */ + notation = ADAM; + break; + case '-': /* Inside throw */ + case '+': /* Outside throw */ + case '=': /* Cross throw */ + case '&': /* Cross catch */ + case 'x': /* Cross throw and catch */ + case '_': /* Bounce */ + type = *p; + break; + case '*': /* Lose ball */ + seen = 1; + h = -1; + /* fall through */ + case ']': /* end Adam notation */ + nn = HEIGHT; + /* fall through */ + case ' ': + if (seen) { + if (!add_throw(sp, type, h, notation)) + return False; + type=' '; + h = 0; + seen = 0; + } + notation = nn; + break; + default: + (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p); + break; + } + } + } + if (seen) { + if (!add_throw(sp, type, h, notation)) + return False; + } + } + return True; +} + +/* + ~~~~\~~~~~\~~~ + \\~\\~\~\\\~~~ + \\~\\\\~\\\~\~ + \\\\\\\\\\\~\\ + +[33134231334021] + +4 4 1 3 12 2 4 1 4 4 13 0 3 1 + +*/ +#define BOUNCEOVER 10 + +static void +adam(jugglestruct *sp) +{ + Trajectory *t, *p; + for(t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == ATCH) { + int a = t->adam; + t->height = 0; + for(p = t->next; a > 0 && p != sp->head; p = p->next) { + if (p->status != ATCH || p->adam < 0 || p->adam>= a) { + a--; + } + t->height++; + } + if(t->height > BOUNCEOVER && t->posn == ' '){ + t->posn = '_'; /* high defaults can be bounced */ + } + t->status = THRATCH; +#if 0 + (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height); +#endif + } + } +} + +/* Split Thratch notation into explicit throws and catches. + Usually Catch follows Throw in same hand, but take care of special + cases. */ + +/* ..n1.. -> .. LTn RT1 LC RC .. */ +/* ..nm.. -> .. LTn LC RTm RC .. */ + +static Bool +part(jugglestruct *sp) +{ + Trajectory *t, *nt, *p; + Hand hand = (LRAND() & 1) ? RIGHT : LEFT; + + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status > THRATCH) { + hand = t->hand; + } else if (t->status == THRATCH) { + char posn = '='; + + /* plausibility check */ + if (t->height <= 2 && t->posn == '_') { + t->posn = ' '; /* no short bounces */ + } + if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) { + t->posn = ' '; /* 1's need close catches */ + } + + switch (t->posn) { + /* throw catch */ + case ' ': /* fall through */ + case '-': posn = '-'; t->posn = '+'; break; + case '+': posn = '+'; t->posn = '-'; break; + case '=': posn = '='; t->posn = '+'; break; + case '&': posn = '+'; t->posn = '='; break; + case 'x': posn = '='; t->posn = '='; break; + case '_': posn = '_'; t->posn = '-'; break; + default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break; + } + hand = (Hand) ((hand + 1) % 2); + t->status = ACTION; + t->hand = hand; + p = t->prev; + + if (t->height == 1) { + p = p->prev; /* early throw */ + } + t->action = CATCH; + INSERT_AFTER(nt, p); + nt->status = ACTION; + nt->action = THROW; + nt->height = t->height; + nt->hand = hand; + nt->posn = posn; + } + } + return True; +} + +/* Connnect up throws and catches to figure out which ball goes where. + Do the same with the juggler's hands. */ + +static void +lob(ModeInfo *mi) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + Trajectory *t, *p; + int h; + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == ACTION) { +#if 0 + (void) fprintf(stderr, (t->action == CATCH) ? "A %c%c%c\n" : "A %c%c%c%d\n", + t->posn, + t->hand ? 'R' : 'L', + (t->action == THROW) ? 'T' : (t->action == CATCH ? 'C' : 'N'), + t->height); +#endif + if (t->action == THROW) { + if (t->type == Empty) { + if (MI_NPIXELS(mi) > 2) { + t->color = 1 + NRAND(MI_NPIXELS(mi) - 2); + } + t->spin = NRAND(5) - 2; + t->degree_offset = NRAND(360); + t->divisions = 2 * ((LRAND() & 1) + 1); + } + + /* search forward for next hand catch */ + for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) { + if (p->action == CATCH) { + if (t->handlink == NULL && p->hand == t->hand) { + t->handlink = p; /* next catch in this hand */ + } + } + } + + if (t->height > 0) { + h = t->height - 1; + + /* search forward for next ball catch */ + for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) { + if (p->action == CATCH) { + if (t->balllink == NULL && --h < 1) { /* caught */ +#if 0 + if (p->type == Full) { + /* dropped */ + } +#endif + t->balllink = p; /* complete trajectory */ + p->type = Full; + p->color = t->color; /* accept catch */ + p->spin = t->spin; + p->degree_offset = t->degree_offset; + p->divisions = t->divisions; + } + } + } + } + t->type = Empty; /* thrown */ + } else if (t->action == CATCH) { + /* search forward for next throw from this hand */ + for (p = t->next; t->handlink == NULL && p != sp->head; p = p->next) { + if (p->action == THROW && p->hand == t->hand) { + p->type = t->type; /* pass ball */ + p->color = t->color; /* pass color */ + p->spin = NRAND(5) - 2; + p->degree_offset = NRAND(360); + p->divisions = 2 * ((LRAND() & 1) + 1); + t->handlink = p; + } + } + } + t->status = LINKEDACTION; + } + } +} + +/* Convert hand position symbols into actual time/space coordinates */ +static void +positions(jugglestruct *sp) +{ + Trajectory *t; + int now = 0; + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == PTHRATCH) { + now = t->start; + } else if (t->status == LINKEDACTION) { + int xo = 0, yo; + + /* time */ + if (t->action == CATCH) { + if (t->type == Empty) { + now += (int) THROW_NULL_INTERVAL; /* failed catch is short */ + } else { + now += THROW_CATCH_INTERVAL; /* successful catch */ + } + } else { + now += (int) CATCH_THROW_INTERVAL; /* throws are very short */ + } + t->start = now; + + /* space */ + yo = ARMLENGTH; + switch (t->posn) { + case '-': xo = SX - POSE; break; + case '_': + case '+': xo = SX + POSE; break; + case '=': xo = - SX - POSE; yo += 2 * POSE; break; + default: (void) fprintf(stderr, "unexpected posn %c\n", t->posn); break; + } + t->x = sp->cx + ((t->hand == LEFT) ? xo : -xo); + t->y = sp->cy + yo; + + t->status = PTHRATCH; + } + } +} + + +/* Private physics functions */ + +static Spline +makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1) +{ + Spline s; + double a, b, c, d; + int x10; + double t10; + + x10 = x1 - x0; + t10 = t1 - t0; + a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10); + b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10); + c = dx0; + d = x0; + s.a = a; + s.b = -3*a*t0 + b; + s.c = (3*a*t0 - 2*b)*t0 + c; + s.d = ((-a*t0 + b)*t0 - c)*t0 +d; + return s; +} + +static double +makeSplinePair(Spline *s1, Spline *s2, + int x0, double dx0, int t0, + int x1, int t1, + int x2, double dx2, int t2) +{ + int x10, x21; + double t21, t10, t20, dx1; + x10 = x1 - x0; + x21 = x2 - x1; + t21 = t2 - t1; + t10 = t1 - t0; + t20 = t2 - t0; + dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21 + - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) / + (2*t10*t21*t20); + *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1); + *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2); + return dx1; +} + +/* Turn abstract timings into physically appropriate ball trajectories. */ +static Bool +projectile(jugglestruct *sp) +{ + Trajectory *t, *n; + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status != PTHRATCH) { + continue; + } + if (t->action == THROW) { + if (t->balllink != NULL) { + if (t->posn == '_') { /* Bounce once */ + double tc, y0, yf, yc, tb, e, i; + + tc = t->balllink->start - t->start; + + yf = sp->cy + FY; + y0 = t->y; + yc = t->balllink->y; + e = 1; /* permissible error in yc */ + + /* + tb = time to bounce + yt = height at catch time after one bounce + one or three roots according to timing + find one by interval bisection + */ + tb = tc; + for(i = tc / 2; i > 0; i/=2){ + double dy, dt, yt; + if(tb == 0){ + (void) fprintf(stderr, "div by zero!\n"); + break; + } + dy = (yf - y0)/tb + 0.5*sp->Gr*tb; + dt = tc - tb; + yt = -COR*dy*dt + 0.5*sp->Gr*dt*dt + yf; + if(yt > yc + e){ + tb-=i; + }else if(yt < yc - e){ + tb+=i; + }else{ + break; + } + } + + { + double t0, dy; + + t->dx = (t->balllink->x - t->x) / tc; + + /* ball follows parabola down */ + INSERT_AFTER(n, t->prev); + n->start = t->start; + n->finish = (int) (t->start + tb); + n->type = Ball; + n->color = t->color; + n->spin = t->spin; + n->degree_offset = t->degree_offset; + n->divisions = t->divisions; + n->status = PREDICTOR; + + t->dy = (yf - y0)/tb - 0.5*sp->Gr*tb; + t0 = n->start; + /* Represent parabola as a degenerate spline - + linear in x, quadratic in y */ + n->xp.a = 0; + n->xp.b = 0; + n->xp.c = t->dx; + n->xp.d = -t->dx*t0 + t->x; + n->yp.a = 0; + n->yp.b = sp->Gr/2; + n->yp.c = -sp->Gr*t0 + t->dy; + n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y; + + + /* ball follows parabola up */ + INSERT_AFTER(n, t->prev); + n->start = (int) (t0 + tb); + n->finish = (int) (t0 + tc); + n->type = Ball; + n->color = t->color; + n->spin = t->spin; + n->degree_offset = t->degree_offset; + n->divisions = t->divisions; + n->status = PREDICTOR; + + n->xp.a = 0; + n->xp.b = 0; + n->xp.c = t->dx; + n->xp.d = -t->dx*t0 + t->x; + + dy = (yf - y0)/tb + 0.5*sp->Gr*tb; + t0 = n->start; + /* Represent parabola as a degenerate spline - + linear in x, quadratic in y */ + n->yp.a = 0; + n->yp.b = sp->Gr/2; + n->yp.c = -sp->Gr*t0 - COR*dy; + n->yp.d = sp->Gr/2*t0*t0 + COR*dy*t0 + yf; + } + + t->status = BPREDICTOR; + + } else { + double t0, dt; + + /* ball follows parabola */ + INSERT_AFTER(n, t->prev); + n->start = t->start; + n->finish = t->balllink->start; + n->type = Ball; + n->color = t->color; + n->spin = t->spin; + n->degree_offset = t->degree_offset; + n->divisions = t->divisions; + n->status = PREDICTOR; + + t0 = n->start; + dt = t->balllink->start - t->start; + t->dx = (t->balllink->x - t->x) / dt; + t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt; + + /* Represent parabola as a degenerate spline - + linear in x, quadratic in y */ + n->xp.a = 0; + n->xp.b = 0; + n->xp.c = t->dx; + n->xp.d = -t->dx*t0 + t->x; + n->yp.a = 0; + n->yp.b = sp->Gr/2; + n->yp.c = -sp->Gr*t0 + t->dy; + n->yp.d = sp->Gr/2*t0*t0 - t->dy*t0 + t->y; + + + t->status = BPREDICTOR; + } + } else { /* Zero Throw */ + t->status = BPREDICTOR; + } + } + } + return True; +} + +/* Turn abstract hand motions into cubic splines. */ +static void +hands(jugglestruct *sp) +{ + Trajectory *t, *u, *v; + for (t = sp->head->next; t != sp->head; t = t->next) { + /* no throw => no velocity */ + if (t->status != BPREDICTOR) { + continue; + } + + u = t->handlink; + if (u == NULL) { /* no next catch */ + continue; + } + v = u->handlink; + if (v == NULL) { /* no next throw */ + continue; + } + + /* double spline takes hand from throw, thru catch, to + next throw */ + + t->finish = u->start; + t->status = PREDICTOR; + + u->finish = v->start; + u->status = PREDICTOR; + + (void) makeSplinePair(&t->xp, &u->xp, + t->x, t->dx, t->start, + u->x, u->start, + v->x, v->dx, v->start); + (void) makeSplinePair(&t->yp, &u->yp, + t->y, t->dy, t->start, + u->y, u->start, + v->y, v->dy, v->start); + + t->status = PREDICTOR; + } +} + +/* Given target x, y find_elbow puts hand at target if possible, + * otherwise makes hand point to the target */ +static void +find_elbow(jugglestruct *sp, XPoint *h, XPoint *e, int x, int y, int z) +{ + double r, h2, t; + + h2 = x*x + y*y + z*z; + if (h2 > 4*ARMLENGTH*ARMLENGTH) { + t = ARMLENGTH/sqrt(h2); + e->x = (short) (t*x); + e->y = (short) (t*y); + h->x = 2 * e->x; + h->y = 2 * e->y; + } else { + r = sqrt((double)(x*x + z*z)); + t = sqrt(4 * ARMLENGTH * ARMLENGTH / h2 - 1); + e->x = (short) (x*(1 - y*t/r)/2); + e->y = (short) ((y + r*t)/2); + h->x = x; + h->y = y; + } +} + +/* NOTE: returned x, y adjusted for arm reach */ +static void +draw_arm(ModeInfo * mi, Hand side, int *x, int *y) +{ + Display *dpy = MI_DISPLAY(mi); + Window win = MI_WINDOW(mi); + GC gc = MI_GC(mi); + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + + int sig = (side == LEFT) ? 1 : -1; + + XSetLineAttributes(dpy, gc, + ARMWIDTH, LineSolid, CapRound, JoinRound); + if (sp->arm[side][0].x != *x || sp->arm[side][0].y != *y) { + XPoint h, e; + XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); + find_elbow(sp, &h, &e, *x - sig*SX - sp->cx, *y - SY - sp->cy, SZ); + XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin); + *x = sp->arm[side][0].x = sp->cx + sig*SX + h.x; + *y = sp->arm[side][0].y = sp->cy + SY + h.y; + sp->arm[side][1].x = sp->cx + sig*SX + e.x; + sp->arm[side][1].y = sp->cy + SY + e.y; + } + XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi)); + XDrawLines(dpy, win, gc, sp->arm[side], 3, CoordModeOrigin); + XSetLineAttributes(dpy, gc, + 1, LineSolid, CapNotLast, JoinRound); +} + +static void +draw_figure(ModeInfo * mi) +{ + Display *dpy = MI_DISPLAY(mi); + Window win = MI_WINDOW(mi); + GC gc = MI_GC(mi); + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + + XSetLineAttributes(dpy, gc, + ARMWIDTH, LineSolid, CapRound, JoinRound); + XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi)); + XDrawLines(dpy, win, gc, sp->figure_path, FIGURE1, CoordModeOrigin); + XDrawSegments(dpy, win, gc, sp->figure_segs, FIGURE2); + XDrawArc(dpy, win, gc, + sp->cx - HED/2, sp->cy + NEY - HED, HED, HED, 0, 64*360); + XSetLineAttributes(dpy, gc, + 1, LineSolid, CapNotLast, JoinRound); +} + + +/* dumps a human-readable rendition of the current state of the juggle + pipeline to stderr for debugging */ +#ifdef OLDDEBUG +static void +dump(jugglestruct *sp) +{ + Trajectory *t; + + for (t = sp->head->next; t != sp->head; t = t->next) { + switch (t->status) { + case THROW: + (void) fprintf(stderr, "T %c%d\n", t->posn, t->height); + break; + case ACTION: + (void) fprintf(stderr, t->action == CATCH?"A %c%c%c\n":"A %c%c%c%d\n", + t->posn, + t->hand ? 'R' : 'L', + (t->action == THROW)?'T':(t->action == CATCH?'C':'N'), + t->height); + break; + case LINKEDACTION: + (void) fprintf(stderr, "L %c%c%c%d %d\n", + t->posn, + t->hand?'R':'L', + (t->action == THROW)?'T':(t->action == CATCH?'C':'N'), + t->height, t->color); + break; + case PTHRATCH: + (void) fprintf(stderr, "O %c%c%c%d %d %2d %6d %6d\n", t->posn, + t->hand?'R':'L', + (t->action == THROW)?'T':(t->action == CATCH?'C':'N'), + t->height, t->type, t->color, + t->start, t->finish); + break; + case PREDICTOR: + (void) fprintf(stderr, "P %c %2d %6d %6d %g\n", + t->type == Ball?'b':t->type == Empty?'e':'f', + t->color, + t->start, t->finish, t->yp.c); + break; + default: + (void) fprintf(stderr, "status %d not implemented\n", t->status); + break; + } + } +} +#endif + +static int get_num_balls(const char *j) +{ + int balls = 0; + const char *p; + int h = 0; + for (p = j; *p; p++) { + if (*p >= '0' && *p <='9') { /* digit */ + h = 10*h + (*p - '0'); + } else { + if (h > balls) { + balls = h; + } + h = 0; + } + } + return balls; +} + +#ifdef __cplusplus +extern "C" { +#endif + +static int compare_num_balls(const void *p1, const void *p2) +{ + int i = get_num_balls(((patternstruct*)p1)->pattern); + int j = get_num_balls(((patternstruct*)p2)->pattern); + if (i > j) { + return (1); + } else if (i < j) { + return (-1); + } else { + return (0); + } +} + +#ifdef __cplusplus +} +#endif + +/* Public functions */ + +void +release_juggle(ModeInfo * mi) +{ + if (juggles != NULL) { + int screen; + + for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) + free_juggle(&juggles[screen]); + (void) free((void *) juggles); + juggles = (jugglestruct *) NULL; + } + if (patternindex != NULL) { + (void) free((void *) patternindex); + patternindex = (PatternIndex *) NULL; + } +} + +void +init_juggle(ModeInfo * mi) +{ + jugglestruct *sp; + int i; + XPoint figure1[FIGURE1]; + XSegment figure2[FIGURE2]; + if (pattern != NULL && *pattern == '.') { + pattern = NULL; + } + if (pattern == NULL && patternindex == NULL) { + /* pattern list needs indexing */ + int i; + int nelements = sizeof(portfolio)/sizeof(patternstruct); + int maxballs; + int numpat = 0; + + /* sort according to number of balls */ + qsort((void*)portfolio, nelements, + sizeof(patternstruct), compare_num_balls); + + /* last pattern has most balls */ + maxballs = get_num_balls(portfolio[nelements - 1].pattern); + /* allocate index */ + if ((patternindex = (PatternIndex *) calloc(maxballs + 1, + sizeof (PatternIndex))) == NULL) { + return; + } + + /* run through sorted list, indexing start of each group + and number in group */ + maxballs = 1; + for (i = 0; i < nelements; i++) { + int b = get_num_balls(portfolio[i].pattern); + if (b > maxballs) { + if (MI_IS_VERBOSE(mi)) { + (void) fprintf(stderr, "%d %d ball pattern%s\n", + numpat, maxballs, (numpat == 1) ? "" : "s"); + } + patternindex[maxballs].number = numpat; + maxballs = b; + numpat = 1; + patternindex[maxballs].start = i; + } else { + numpat++; + } + } + if (MI_IS_VERBOSE(mi)) { + (void) fprintf(stderr, "%d %d ball pattern%s\n", + numpat, maxballs, (numpat == 1) ? "" : "s"); + } + patternindex[maxballs].number = numpat; + } + + if (juggles == NULL) { /* allocate jugglestruct */ + if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi), + sizeof (jugglestruct))) == NULL) { + release_juggle(mi); + return; + } + } + sp = &juggles[MI_SCREEN(mi)]; + + sp->count = 0; + + if (MI_IS_FULLRANDOM(mi)) { + sp->solid = (Bool) (LRAND() & 1); +#ifdef UNI + sp->uni = (Bool) (LRAND() & 1); +#endif + } else { + sp->solid = solid; +#ifdef UNI + sp->uni = uni; +#endif + } + + sp->width = MI_WIDTH(mi); + sp->height = MI_HEIGHT(mi); + sp->count = ABS(MI_COUNT(mi)); + if (sp->count == 0) + sp->count = 150; + sp->scale = sp->height / 480.0; + /* vary x a little so the juggler does not burn the screen */ + sp->cx = sp->width / 2 + RFX + NRAND(LFX - RFX + 1); + sp->cy = sp->height - FY - ((int) sp->uni) * FY / 3; /* raise higher */ + /* "7" hits top of screen */ + sp->Gr = GRAVITY(sp->cy, 7 * THROW_CATCH_INTERVAL); + + figure1[0].x = LHIPX, figure1[0].y = HIPY; + figure1[1].x = 0, figure1[1].y = WSTY; + figure1[2].x = SX, figure1[2].y = SY; + figure1[3].x = -SX, figure1[3].y = SY; + figure1[4].x = 0, figure1[4].y = WSTY; + figure1[5].x = RHIPX, figure1[5].y = HIPY; + figure1[6].x = LHIPX, figure1[6].y = HIPY; + figure2[0].x1 = 0, figure2[0].y1 = SY, + figure2[0].x2 = 0, figure2[0].y2 = NEY; + figure2[1].x1 = LHIPX, figure2[1].y1 = HIPY, + figure2[1].x2 = LFX, figure2[1].y2 = FY; + figure2[2].x1 = RHIPX, figure2[2].y1 = HIPY, + figure2[2].x2 = RFX, figure2[2].y2 = FY; + + /* Body Path */ + for (i = 0; i < FIGURE1; i++) { + sp->figure_path[i].x = figure1[i].x + sp->cx; + sp->figure_path[i].y = figure1[i].y + sp->cy; + } + /* Body Segments */ + for (i = 0; i < FIGURE2; i++) { + sp->figure_segs[i].x1 = figure2[i].x1 + sp->cx; + sp->figure_segs[i].y1 = figure2[i].y1 + sp->cy; + sp->figure_segs[i].x2 = figure2[i].x2 + sp->cx; + sp->figure_segs[i].y2 = figure2[i].y2 + sp->cy; + } + /* Shoulders */ + sp->arm[LEFT][2].x = sp->cx + SX; + sp->arm[LEFT][2].y = sp->cy + SY; + sp->arm[RIGHT][2].x = sp->cx - SX; + sp->arm[RIGHT][2].y = sp->cy + SY; + + if (sp->trace == NULL) { + if ((sp->trace = (XPoint *)calloc(trail, sizeof(XPoint))) == NULL) { + free_juggle(sp); + return; + } + } + + /* Clear the background. */ + MI_CLEARWINDOW(mi); + + draw_figure(mi); + + /* record start time */ + sp->begintime = time(NULL); + + free_pattern(sp); + + /* create circular list */ + INSERT_AFTER_TOP(sp->head, sp->head); + + /* generate pattern */ + if (pattern == NULL) { + +#define MAXPAT 10 +#define MAXREPEAT 30 +#define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */ +#define POSITION_BIAS 20 /* larger makes hand movements less likely */ + + int count = 0; + int num_balls = MINBALLS + NRAND(MAXBALLS - MINBALLS); + while (count < MI_CYCLES(mi)) { + char buf[MAXPAT * 3 + 3], *b = buf; + int maxseen = 0; + int l = NRAND(MAXPAT) + 1; + int t = NRAND(MAXREPEAT) + 1; + + { /* vary number of balls */ + int new_balls = num_balls; + int change; + + if (new_balls == 2) /* Do not juggle 2 that often */ + change = NRAND(2 + CHANGE_BIAS / 4); + else + change = NRAND(2 + CHANGE_BIAS); + switch (change) { + case 0: + new_balls++; + break; + case 1: + new_balls--; + break; + default: + break; /* NO-OP */ + } + if (new_balls < MINBALLS) { + new_balls += 2; + } + if (new_balls > MAXBALLS) { + new_balls -= 2; + } + if (new_balls < num_balls) { + if (!program(mi, "[*]", 1)) /* lose ball */ + return; + } + num_balls = new_balls; + } + count++; + + if (NRAND(2) && patternindex[num_balls].number) { + /* Pick from PortFolio */ + if (!program(mi, + portfolio[patternindex[num_balls].start + + NRAND(patternindex[num_balls].number)].pattern, + t)) + return; + } else { + /* Invent a new pattern */ + *b++='['; + for(i = 0; i < l; i++){ + int n, m; + do { /* Triangular Distribution => high values more likely */ + m = NRAND(num_balls + 1); + n = NRAND(num_balls + 1); + } while(m >= n); + if (n == num_balls) { + maxseen = 1; + } + switch(NRAND(6 + POSITION_BIAS)){ + case 0: /* Inside throw */ + *b++ = '-'; break; + case 1: /* Outside throw */ + *b++ = '+'; break; + case 2: /* Cross throw */ + *b++ = '='; break; + case 3: /* Cross catch */ + *b++ = '&'; break; + case 4: /* Cross throw and catch */ + *b++ = 'x'; break; + case 5: /* Bounce */ + *b++ = '_'; break; + default: + break; /* NO-OP */ + } + + *b++ = n + '0'; + *b++ = ' '; + } + *b++ = ']'; + *b = '\0'; + if (maxseen) { + if (!program(mi, buf, t)) + return; + } + } + } + } else { /* pattern supplied in height or 'a' notation */ + if (!program(mi, pattern, MI_CYCLES(mi))) + return; + } + + adam(sp); + + if (!part(sp)) + return; + + lob(mi); + + positions(sp); + + if (!projectile(sp)) + return; + + hands(sp); + +#ifdef OLDDEBUG + dump(sp); +#endif +} + +#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d) + +#ifdef SUNOS4 +/*- + * Workaround SunOS 4 framebuffer bug which causes balls to leave dust + * trace behind when erased + */ +#define ERASE_BALL(x,y) \ + XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \ + XFillArc(dpy, window, gc, \ + (x) - BALLRADIUS - 2, (y) - BALLRADIUS - 2, \ + 2*(BALLRADIUS + 2), 2*(BALLRADIUS + 2), 0, 23040) +#else + +#define ERASE_BALL(x,y) \ + XSetForeground(dpy, gc, MI_BLACK_PIXEL(mi)); \ + XFillArc(dpy, window, gc, \ + (x) - BALLRADIUS, (y) - BALLRADIUS, \ + 2*BALLRADIUS, 2*BALLRADIUS, 0, 23040) +#endif + +static void +draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions) +{ + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + GC gc = MI_GC(mi); + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + int offset; + + XSetForeground(dpy, gc, color); + if ((color == MI_WHITE_PIXEL(mi)) || + ((divisions != 2) && (divisions != 4)) || sp->solid) { + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + 0, 23040); + return; + } + offset = (int) (degree_offset * 64); + if (divisions == 4) { /* 90 degree divisions */ + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + offset, 5760); + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 11520) % 23040, 5760); + XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi)); + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 5760) % 23040, 5760); + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 17280) % 23040, 5760); + } else { /* 180 degree divisions */ + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + offset, 11520); + XSetForeground(dpy, gc, MI_WHITE_PIXEL(mi)); + XFillArc(dpy, window, gc, + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 11520) % 23040, 11520); + } + XFlush(dpy); +} + +void +draw_juggle(ModeInfo * mi) +{ + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + GC gc = MI_GC(mi); + Trajectory *traj; + int future = 0; + int length = 0; + jugglestruct *sp; + + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; + if (sp->trace == NULL) + return; + + MI_IS_DRAWN(mi) = True; + + draw_figure(mi); + + { + struct timeval tv; + (void)gettimeofday(&tv, NULL); + sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000); + } + for (traj = sp->head->next; traj != sp->head; traj = traj->next) { + length++; + if (traj->status != PREDICTOR) { + continue; + } + if (traj->start > future) { + future = traj->start; + } + if (sp->time < traj->start) { /* early */ + continue; + } else if (sp->time < traj->finish) { /* working */ + int x = (int) CUBIC(traj->xp, sp->time); + int y = (int) CUBIC(traj->yp, sp->time); + unsigned long color; + + if (MI_NPIXELS(mi) > 2) { + color = MI_PIXEL(mi, traj->color); + } else { + color = MI_WHITE_PIXEL(mi); + } + if (traj->type == Empty || traj->type == Full) { + draw_arm(mi, traj->hand, &x, &y); + } + if (traj->type == Ball || traj->type == Full) { + if(trail > 0) { + ERASE_BALL(sp->trace[sp->traceindex].x, + sp->trace[sp->traceindex].y); + sp->trace[sp->traceindex].x = traj->x; + sp->trace[sp->traceindex].y = traj->y; + if (++sp->traceindex >= trail) { + sp->traceindex = 0; + } + } else { + ERASE_BALL(traj->x, traj->y); + } + draw_juggle_ball(mi, color, x, y, traj->degree_offset, traj->divisions); + traj->degree_offset = traj->degree_offset + + SPIN_DEGREES * traj->spin / sp->count; + if (traj->degree_offset < 0.0) + traj->degree_offset += 360.0; + else if (traj->degree_offset >= 360.0) + traj->degree_offset -= 360.0; + } + traj->x = x; + traj->y = y; + } else { /* expired */ + Trajectory *n = traj; + + ERASE_BALL(traj->x, traj->y); + traj=traj->prev; + REMOVE(n); + } + } + + /*** FIXME-BEGIN ***/ + /* pattern generator would refill here when necessary */ +#if 1 + if (future == 0) { +#else + if (sp->count > MI_CYCLES(mi)) { /* pick a new juggle */ +#endif + init_juggle(mi); + } + /*** FIXME-END ***/ + +} + +#endif /* MODE_juggle */