X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fjuggle.c;h=a0943fb21f80ee1498095c01708991e3af56d4d8;hp=3b3773225c684d43cbac415437d86e49c3a10e58;hb=2d04c4f22466851aedb6ed0f2919d148f726b889;hpb=bc7b7a8eb122206d239ec0e693676bcce31be1aa diff --git a/hacks/juggle.c b/hacks/juggle.c index 3b377322..a0943fb2 100644 --- a/hacks/juggle.c +++ b/hacks/juggle.c @@ -1,8 +1,9 @@ /* -*- Mode: C; tab-width: 4 -*- */ /* juggle */ -#if 0 -static const char sccsid[] = "@(#)juggle.c 5.00 2000/11/01 xlockmore"; +#if !defined( lint ) && !defined( SABER ) +static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore"; + #endif /*- @@ -21,156 +22,251 @@ static const char sccsid[] = "@(#)juggle.c 5.00 2000/11/01 xlockmore"; * other special, indirect and consequential damages. * * Revision History + * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner. + * Add -rings, -bballs. Add -describe. Finally made + * live pattern updates possible. Add refill_juggle(), + * change_juggle() and reshape_juggle(). Make + * init_juggle() non-destructive. Reorder erase/draw + * operations. Update xscreensaver xml and manpage. + * 15-Nov-2004: [TDA] Fix all memory leaks. + * 12-Nov-2004: [TDA] Add -torches and another new trail + * implementation, so that different objects can have + * different length trails. + * 11-Nov-2004: [TDA] Clap when all the balls are in the air. + * 10-Nov-2004: [TDA] Display pattern name converted to hight + * notation. + * 31-Oct-2004: [TDA] Add -clubs and new trail implementation. + * 02-Sep-2003: Non-real time to see what is happening without a + * strobe effect for slow machines. * 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 + * Implement the anonymously promised -uni option. */ -/*- -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'.] -*/ +/* + * 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'.] + */ + +/* This code uses so many linked lists it's worth having a built-in + * leak-checker */ +#undef MEMTEST #ifdef STANDALONE #define MODE_juggle #define PROGCLASS "Juggle" #define HACK_INIT init_juggle #define HACK_DRAW draw_juggle +#define HACK_RESHAPE reshape_juggle +#define _no_HACK_FREE release_juggle #define juggle_opts xlockmore_opts #define DEFAULTS "*delay: 10000 \n" \ -"*count: 150 \n" \ -"*cycles: 30 \n" \ -"*ncolors: 32 \n" -#define SMOOTH_COLORS +"*count: 200 \n" \ +"*cycles: 1000 \n" \ +"*ncolors: 32 \n" \ +"*font: -*-times-bold-r-normal-*-180-*\n" +#undef SMOOTH_COLORS #include "xlockmore.h" /* in xscreensaver distribution */ +#define MI_DELAY(MI) ((MI)->pause) +# ifndef MI_DEPTH +# define MI_DEPTH MI_WIN_DEPTH +# endif #else /* STANDALONE */ #include "xlock.h" /* in xlockmore distribution */ #endif /* STANDALONE */ #ifdef MODE_juggle +#if 0 +#define XClearWindow(d, w) \ +{ \ + XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \ + XFillRectangle(d, w, MI_GC(mi), \ + 0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \ +} +#endif + #define DEF_PATTERN "." /* All patterns */ -#define DEF_TRAIL "0" /* No trace */ +#define DEF_TAIL "1" /* No trace */ #ifdef UNI -#define DEF_UNI "FALSE" /* No unicycle */ /* Not implemented yet */ +/* Maybe a ROLA BOLA would be at a better angle for viewing */ +#define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */ +#endif +#define DEF_REAL "True" +#define DEF_DESCRIBE "True" + +#define DEF_BALLS "True" /* Use Balls */ +#define DEF_CLUBS "True" /* Use Clubs */ +#define DEF_TORCHES "True" /* Use Torches */ +#define DEF_KNIVES "True" /* Use Knives */ +#define DEF_RINGS "True" /* Use Rings */ +#define DEF_BBALLS "True" /* Use Bowling Balls */ + +#ifndef XtNumber +#define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0]))) #endif -#define DEF_SOLID "FALSE" /* Not solid */ static char *pattern; -static int trail; +static int tail; #ifdef UNI static Bool uni; #endif -static Bool solid; +static Bool real; +static Bool describe; +static Bool balls; +static Bool clubs; +static Bool torches; +static Bool knives; +static Bool rings; +static Bool bballs; +static char *only; static XrmOptionDescRec opts[] = { - {(char* ) "-pattern", ".juggle.pattern", XrmoptionSepArg, NULL}, - {(char* ) "-trail", ".juggle.trail", XrmoptionSepArg, NULL}, + {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL }, + {"-tail", ".juggle.tail", XrmoptionSepArg, NULL }, #ifdef UNI - {"-uni", ".juggle.uni", XrmoptionNoArg, "on"}, - {"+uni", ".juggle.uni", XrmoptionNoArg, "off"}, + {"-uni", ".juggle.uni", XrmoptionNoArg, "on" }, + {"+uni", ".juggle.uni", XrmoptionNoArg, "off" }, #endif - {"-solid", ".juggle.solid", XrmoptionNoArg, "on"}, - {"+solid", ".juggle.solid", XrmoptionNoArg, "off"} + {"-real", ".juggle.real", XrmoptionNoArg, "on" }, + {"+real", ".juggle.real", XrmoptionNoArg, "off" }, + {"-describe", ".juggle.describe", XrmoptionNoArg, "on" }, + {"+describe", ".juggle.describe", XrmoptionNoArg, "off" }, + {"-balls", ".juggle.balls", XrmoptionNoArg, "on" }, + {"+balls", ".juggle.balls", XrmoptionNoArg, "off" }, + {"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" }, + {"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" }, + {"-torches", ".juggle.torches", XrmoptionNoArg, "on" }, + {"+torches", ".juggle.torches", XrmoptionNoArg, "off" }, + {"-knives", ".juggle.knives", XrmoptionNoArg, "on" }, + {"+knives", ".juggle.knives", XrmoptionNoArg, "off" }, + {"-rings", ".juggle.rings", XrmoptionNoArg, "on" }, + {"+rings", ".juggle.rings", XrmoptionNoArg, "off" }, + {"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" }, + {"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" }, + {"-only", ".juggle.only", XrmoptionSepArg, NULL }, }; static argtype vars[] = { - {&pattern, "pattern", - "Pattern", (char *) DEF_PATTERN, t_String}, - {&trail, "trail", "Trail", DEF_TRAIL, t_Int}, + { &pattern, "pattern", "Pattern", DEF_PATTERN, t_String }, + { &tail, "tail", "Tail", DEF_TAIL, t_Int }, #ifdef UNI - {&uni, "uni", "Uni", DEF_UNI, t_Bool}, + { &uni, "uni", "Uni", DEF_UNI, t_Bool }, #endif - {&solid, "solid", "Solid", DEF_SOLID, t_Bool} + { &real, "real", "Real", DEF_REAL, t_Bool }, + { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool }, + { &balls, "balls", "Clubs", DEF_BALLS, t_Bool }, + { &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool }, + { &torches, "torches", "Torches", DEF_TORCHES, t_Bool }, + { &knives, "knives", "Knives", DEF_KNIVES, t_Bool }, + { &rings, "rings", "Rings", DEF_RINGS, t_Bool }, + { &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool }, + { &only, "only", "BBalls", " ", t_String }, }; static OptionStruct desc[] = { - {"-pattern string", "Cambridge Juggling Pattern"}, - {"-trail num", "Trace Juggling Patterns"}, + { "-pattern string", "Cambridge Juggling Pattern" }, + { "-tail num", "Trace Juggling Patterns" }, #ifdef UNI - {"-/+uni", "Unicycle"}, + { "-/+uni", "Unicycle" }, #endif - {"-/+solid", "solid color (else its a 4 panel look (half white))"} + { "-/+real", "Real-time" }, + { "-/+describe", "turn on/off pattern descriptions." }, + { "-/+balls", "turn on/off Balls." }, + { "-/+clubs", "turn on/off Clubs." }, + { "-/+torches", "turn on/off Flaming Torches." }, + { "-/+knives", "turn on/off Knives." }, + { "-/+rings", "turn on/off Rings." }, + { "-/+bballs", "turn on/off Bowling Balls." }, + { "-only", "Turn off all objects but the named one." }, }; ModeSpecOpt juggle_opts = -{sizeof opts / sizeof opts[0], opts, - sizeof vars / sizeof vars[0], vars, desc}; + { XtNumber(opts), opts, XtNumber(vars), 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, "", + "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts, + 10000, 200, 1000, 1, 64, 1.0, "", "Shows a Juggler, juggling", 0, NULL }; @@ -179,7 +275,6 @@ ModStruct juggle_description = { #ifdef USE_XVMSUTILS #include #endif -#include #if HAVE_SYS_TIME_H #include #else @@ -188,55 +283,172 @@ ModStruct juggle_description = { #endif #endif +/* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All + "thicknesses" are scaled by sqrt(sp->scale) so that they are + proportionally thicker for smaller windows. Objects spinning out + of the plane (such as clubs) fake perspective by compressing their + horizontal coordinates by PERSPEC */ + /* Figure */ -#define ARMLENGTH ((int) (40.0 * sp->scale)) +#define ARMLENGTH 50 #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 POSE 10 #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 PERSPEC 0.4 +/* macros */ #define GRAVITY(h, t) 4*(double)(h)/((t)*(t)) +/* Timing based on count. Units are milliseconds. Juggles per second + is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */ + #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) */ +/******************************************************************** + * Trace Definitions * + * * + * These record rendering data so that a drawn object can be erased * + * later. Each object has its own Trace list. * + * * + ********************************************************************/ + +typedef struct {double x, y; } DXPoint; +typedef struct trace *TracePtr; +typedef struct trace { + TracePtr next, prev; + double x, y; + double angle; + int divisions; + DXPoint dlast; +#ifdef MEMTEST + char pad[1024]; +#endif +} Trace; + +/******************************************************************* + * Object Definitions * + * * + * These describe the various types of Object that can be juggled * + * * + *******************************************************************/ +typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *); + +static DrawProc show_ball, show_europeanclub, show_torch, show_knife; +static DrawProc show_ring, show_bball; + +typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS, + NUM_OBJECT_TYPES} ObjType; +#define OBJMIXPROB 20 /* inverse of the chances of using an odd + object in the pattern */ + +static const struct { + DrawProc *draw; /* Object Rendering function */ + int handle; /* Length of object's handle */ + int mintrail; /* Minimum trail length */ + double cor; /* Coefficient of Restitution. perfect bounce = 1 */ + double weight; /* Heavier objects don't get thrown as high */ +} ObjectDefs[] = { + { /* Ball */ + show_ball, + 0, + 1, + 0.9, + 1.0, + }, + { /* Club */ + show_europeanclub, + 15, + 1, + 0.55, /* Clubs don't bounce too well */ + 1.0, + }, + { /* Torch */ + show_torch, + 15, + 20, /* Torches need flames */ + 0, /* Torches don't bounce -- fire risk! */ + 1.0, + }, + { /* Knife */ + show_knife, + 15, + 1, + 0, /* Knives don't bounce */ + 1.0, + }, + { /* Ring */ + show_ring, + 15, + 1, + 0.8, + 1.0, + }, + { /* Bowling Ball */ + show_bball, + 0, + 1, + 0.2, + 5.0, + }, +}; -/* typedefs */ +/************************** + * Trajectory definitions * + **************************/ 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 enum {THROW, CATCH} Action; +typedef enum {HAND, ELBOW, SHOULDER} Joint; +typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION, + PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus; typedef struct {double a, b, c, d; } Spline; +typedef DXPoint Arm[3]; + +/* A Wander contains a Spline and a time interval. A list of Wanders + * describes the performer's position as he moves around the screen. */ +typedef struct wander *WanderPtr; +typedef struct wander { + WanderPtr next, prev; + double x; + unsigned long finish; + Spline s; +#ifdef MEMTEST + char pad[1024]; +#endif +} Wander; + +/* Object is an arbitrary object being juggled. Each Trajectory + * references an Object ("count" tracks this), and each Object is also + * linked into a global Objects list. Objects may include a Trace + * list for tracking erasures. */ +typedef struct object *ObjectPtr; +typedef struct object { + ObjectPtr next, prev; + + ObjType type; + int color; + int count; /* reference count */ + Bool active; /* Object is in use */ + + Trace *trace; + int tracelen; + int tail; +#ifdef MEMTEST + char pad[1024]; +#endif +} Object; +/* Trajectory is a segment of juggling action. A list of Trajectories + * defines the juggling performance. The Trajectory list goes through + * multiple processing steps to convert it from basic juggling + * notation into rendering data. */ + +typedef struct trajectory *TrajectoryPtr; typedef struct trajectory { TrajectoryPtr prev, next; /* for building list */ TrajectoryStatus status; @@ -245,6 +457,8 @@ typedef struct trajectory { char posn; int height; int adam; + char *pattern; + char *name; /* Action */ Hand hand; @@ -252,166 +466,256 @@ typedef struct trajectory { /* LinkedAction */ int color; - int spin, divisions; - double degree_offset; + Object *object; + int divisions; + double angle, spin; TrajectoryPtr balllink; TrajectoryPtr handlink; /* PThratch */ - - double dx; /* initial velocity */ - double dy; + double cx; /* Moving juggler */ + double x, y; /* current position */ + double dx, dy; /* initial velocity */ /* Predictor */ Throwable type; - int start, finish; + unsigned long start, finish; Spline xp, yp; - int x, y; /* current position */ + +#ifdef MEMTEST + char pad[1024]; +#endif } Trajectory; -/* structs */ +/* Jugglestruct: per-screen global data. The master Wander, Object + * and Trajectory lists are anchored here. */ 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; + double scale; + Wander *wander; + double cx; + double Gr; + Trajectory *head; + Arm arm[2][2]; + char *pattern; + int count; + int num_balls; + time_t begintime; /* should make 'time' usable for at least 48 days + on a 32-bit machine */ + unsigned long time; /* millisecond timer*/ + ObjType objtypes; + Object *objects; } jugglestruct; static jugglestruct *juggles = (jugglestruct *) NULL; -typedef struct { - char * pattern; - char * name; -} patternstruct; +static XFontStruct *mode_font = None; -#define MINBALLS 2 -#define MAXBALLS 7 +/******************* + * Pattern Library * + *******************/ typedef struct { - int start; - int number; -} PatternIndex; - -static PatternIndex* patternindex = (PatternIndex *) NULL; + const char * pattern; + const char * name; +} patternstruct; /* List of popular patterns, in any order */ +/* Patterns should be given in Adam notation so the generator can + concatenate them safely. Null descriptions are ok. Height + notation will be displayed automatically. */ static patternstruct portfolio[] = { - {"[+2 1]", "+3 1, Typical 2 ball juggler"}, - {"[2 0]", "4 0, 2 balls 1 hand"}, - {"[2 0 1]", "5 0 1"}, - {"[+2 0 +2 0 0]", "+5 0 +5 0 0"}, - {"[3]", "3, cascade"}, - {"[+3]", "+3, reverse cascade"}, - {"[=3]", "=3, cascade under arm"}, - {"[&3]", "&3, cascade catching under arm"}, - {"[_3]", "_3, bouncing cascade"}, - {"[+3 x3 =3]", "+3 x3 =3, Mill's mess"}, - {"[3 2 1]", "5 3 1"}, - {"[3 3 1]", "4 4 1"}, - {"[3 1 2]", "6 1 2, See-saw"}, - {"[=3 3 1 2]", "=4 5 1 2"}, - {"[=3 2 2 3 1 2]", "=6 2 2 5 1 2, =4 5 1 2 stretched"}, - {"[+3 3 1 3]", "+4 4 1 3, anemic shower box"}, - {"[3 3 1]", "4 4 1"}, - {"[+3 2 3]", "+4 2 3"}, - {"[+3 1]", "+5 1, 3 shower"}, - {"[_3 1]", "_5 1, bouncing 3 shower"}, - {"[3 0 3 0 3]", "5 0 5 0 5, shake 3 out of 5"}, - {"[3 3 3 0 0]", "5 5 5 0 0, flash 3 out of 5"}, - {"[3 3 0]", "4 5 0, complete waste of a 5 ball juggler"}, - {"[3 3 3 0 0 0 0]", "7 7 7 0 0 0 0, 3 flash"}, - {"[+3 0 +3 0 +3 0 0]", "+7 0 +7 0 +7 0 0"}, - {"[4]", "4, 4 cascade"}, - {"[+4 3]", "+5 3, 4 ball half shower"}, - {"[4 4 2]", "5 5 2"}, - {"[+4 4 4 +4]", "+4 4 4 +4, 4 columns"}, - {"[4 3 +4]", "5 3 +4"}, - {"[+4 1]", "+7 1, 4 shower"}, - {"[4 4 4 4 0]", "5 5 5 5 0, learning 5"}, - {"[5]", "5, 5 cascade"}, - {"[_5 _5 _5 _5 _5 5 5 5 5 5]", "_5 _5 _5 _5 _5 5 5 5 5 5, jump rope"}, - {"[+5 x5 =5]", "+5 x5 =5, Mill's mess for 5"}, - {"[6]", "6, 6 cascade"}, - {"[7]", "7, 7 cascade"}, - {"[_7]", "_7, bouncing 7 cascade"}, + {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"}, + {"[2 0]", /* 4 0 */ "2 in 1 hand"}, + {"[2 0 1]", /* 5 0 1 */}, + {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */}, + {"[+2 0 1 2 2]", /* +4 0 1 2 3 */}, + {"[2 0 1 1]", /* 6 0 1 1 */}, + + {"[3]", /* 3 */ "3 cascade"}, + {"[+3]", /* +3 */ "reverse 3 cascade"}, + {"[=3]", /* =3 */ "cascade 3 under arm"}, + {"[&3]", /* &3 */ "cascade 3 catching under arm"}, + {"[_3]", /* _3 */ "bouncing 3 cascade"}, + {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"}, + {"[3 2 1]", /* 5 3 1" */}, + {"[3 3 1]", /* 4 4 1" */}, + {"[3 1 2]", /* 6 1 2 */ "See-saw"}, + {"[=3 3 1 2]", /* =4 5 1 2 */}, + {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"}, + {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"}, + {"[3 3 1]", /* 4 4 1 */}, + {"[+3 2 3]", /* +4 2 3 */}, + {"[+3 1]", /* +5 1 */ "3 shower"}, + {"[_3 1]", /* _5 1 */ "bouncing 3 shower"}, + {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"}, + {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"}, + {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"}, + {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"}, + {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */}, + {"[3 2 2 0 3 2 0 2 3 0 2 2 0]", /* 7 3 3 0 7 3 0 3 7 0 3 3 0 */}, + {"[3 0 2 0]", /* 8 0 4 0 */}, + {"[_3 2 1]", /* _5 3 1 */}, + {"[_3 0 1]", /* _8 0 1 */}, + {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */}, + {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */}, + + {"[4]", /* 4 */ "4 cascade"}, + {"[+4 3]", /* +5 3 */ "4 ball half shower"}, + {"[4 4 2]", /* 5 5 2 */}, + {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"}, + {"[+4 3 +4]", /* +5 3 +4 */}, + {"[4 3 4 4]", /* 5 3 4 4 */}, + {"[4 3 3 4]", /* 6 3 3 4 */}, + {"[4 3 2 4", /* 6 4 2 4 */}, + {"[+4 1]", /* +7 1 */ "4 shower"}, + {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"}, + {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"}, + {"[+4 2 1 3]", /* +9 3 1 3 */}, + {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */}, + {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */}, + {"[_4 3 3]", /* _6 3 3 */}, + {"[_4 3 1]", /* _7 4 1 */}, + {"[_4 2 1]", /* _8 3 1 */}, + {"[_4 3 3 3 0]", /* _8 4 4 4 0 */}, + {"[_4 1 3 1]", /* _9 1 5 1 */}, + {"[_4 1 3 1 2]", /* _10 1 6 1 2 */}, + + {"[5]", /* 5 */ "5 cascade"}, + {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */}, + {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"}, + {"[5 4 4]", /* 7 4 4 */}, + {"[_5 4 4]", /* _7 4 4 */}, + {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"}, + {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */}, + {"[_5 4 1 +4]", /* _9 5 1 5 */}, + {"[_5 4 +4 +4]", /* _8 4 +4 +4 */}, + {"[_5 4 4 4 1]", /* _9 5 5 5 1 */}, + {"[_5 4 4 5 1]",}, + {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */}, + + {"[6]", /* 6 */ "6 cascade"}, + {"[+6 5]", /* +7 5 */}, + {"[6 4]", /* 8 4 */}, + {"[+6 3]", /* +9 3 */}, + {"[6 5 4 4]", /* 9 7 4 4 */}, + {"[+6 5 5 5]", /* +9 5 5 5 */}, + {"[6 0 6]", /* 9 0 9 */}, + {"[_6 0 _6]", /* _9 0 _9 */}, + + {"[_7]", /* _7 */ "bouncing 7 cascade"}, + {"[7]", /* 7 */ "7 cascade"}, + {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"}, + }; -/* 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 +typedef struct { int start; int number; } PatternIndex; + +static struct { + int minballs; + int maxballs; + PatternIndex index[XtNumber(portfolio)]; +} patternindex; + +/******************* + * list management * + *******************/ + +#define DUP_OBJECT(n, t) { \ + (n)->object = (t)->object; \ + if((n)->object != NULL) (n)->object->count++; \ +} + +/* t must point to an existing element. 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) +#define REMOVE(t) { \ + (t)->next->prev = (t)->prev; \ + (t)->prev->next = (t)->next; \ + free(t); \ +} -static void -free_pattern(jugglestruct *sp) { - if (sp->head != NULL) { - while (sp->head->next != sp->head) { - Trajectory *t = sp->head->next; +/* t receives element to be created and added to the list. ot must + point to an existing element or be identical to t to start a new + list. Applicable to Trajectories, Objects and Traces. */ +#define ADD_ELEMENT(type, t, ot) \ + if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \ + (t)->next = (ot)->next; \ + (t)->prev = (ot); \ + (ot)->next = (t); \ + (t)->next->prev = (t); \ + } - REMOVE(t); /* don't eliminate t */ - } - (void) free((void *) sp->head); - sp->head = (Trajectory *) NULL; +static void +object_destroy(Object* o) +{ + if(o->trace != NULL) { + while(o->trace->next != o->trace) { + Trace *s = o->trace->next; + REMOVE(s); /* Don't eliminate 's' */ } + free(o->trace); + } + REMOVE(o); } static void -free_juggle(jugglestruct *sp) -{ - if (sp->trace != NULL) { - (void) free((void *) sp->trace); - sp->trace = (XPoint *) NULL; +trajectory_destroy(Trajectory *t) { + if(t->name != NULL) free(t->name); + if(t->pattern != NULL) free(t->pattern); + /* Reduce object link count and call destructor if necessary */ + if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) { + object_destroy(t->object); + } + REMOVE(t); /* Unlink and free */ +} + +static void +free_juggle(jugglestruct *sp) { + if (sp->head != NULL) { + while (sp->head->next != sp->head) { + trajectory_destroy(sp->head->next); + } + free(sp->head); + sp->head = (Trajectory *) NULL; + } + if(sp->objects != NULL) { + while (sp->objects->next != sp->objects) { + object_destroy(sp->objects->next); + } + free(sp->objects); + sp->objects = (Object*)NULL; + } + if(sp->wander != NULL) { + while (sp->wander->next != sp->wander) { + Wander *w = sp->wander->next; + REMOVE(w); } - free_pattern(sp); + free(sp->wander); + sp->wander = (Wander*)NULL; + } + if(sp->pattern != NULL) { + free(sp->pattern); + sp->pattern = NULL; + } } static Bool -add_throw(jugglestruct *sp, char type, int h, Notation n) +add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name) { Trajectory *t; - INSERT_AFTER(t, sp->head->prev); + ADD_ELEMENT(Trajectory, t, sp->head->prev); + if(t == NULL){ /* Out of Memory */ + free_juggle(sp); + return False; + } + t->object = NULL; + if(name != NULL) + t->name = strdup(name); t->posn = type; if (n == ADAM) { t->adam = h; + t->height = 0; t->status = ATCH; } else { t->height = h; @@ -422,25 +726,31 @@ add_throw(jugglestruct *sp, char type, int h, Notation n) /* add a Thratch to the performance */ static Bool -program(ModeInfo *mi, const char *patn, int repeat) +program(ModeInfo *mi, const char *patn, const char *name, int cycles) { jugglestruct *sp = &juggles[MI_SCREEN(mi)]; const char *p; - int h, i, seen; + int w, h, i, seen; Notation notation; char type; if (MI_IS_VERBOSE(mi)) { - (void) fprintf(stderr, "%s x %d\n", patn, repeat); + (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n", + MI_SCREEN(mi), (name == NULL) ? patn : name, cycles); } - for(i=0; i < repeat; i++) { + for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws + have been programmed */ + /* title is the pattern name to be supplied to the first throw of + a sequence. If no name if given, use an empty title so that + the sequences are still delimited. */ + const char *title = (name != NULL)? name : ""; type=' '; h = 0; seen = 0; notation = HEIGHT; for(p=patn; *p; p++) { - if (*p >= '0' && *p <='9') { + if (*p >= '0' && *p <='9') { seen = 1; h = 10*h + (*p - '0'); } else { @@ -450,11 +760,14 @@ program(ModeInfo *mi, const char *patn, int repeat) notation = ADAM; break; case '-': /* Inside throw */ + type = ' '; + break; case '+': /* Outside throw */ case '=': /* Cross throw */ case '&': /* Cross catch */ case 'x': /* Cross throw and catch */ case '_': /* Bounce */ + case 'k': /* Kickup */ type = *p; break; case '*': /* Lose ball */ @@ -466,8 +779,10 @@ program(ModeInfo *mi, const char *patn, int repeat) /* fall through */ case ' ': if (seen) { - if (!add_throw(sp, type, h, notation)) + i++; + if (!add_throw(sp, type, h, notation, title)) return False; + title = NULL; type=' '; h = 0; seen = 0; @@ -475,17 +790,22 @@ program(ModeInfo *mi, const char *patn, int repeat) notation = nn; break; default: - (void) fprintf(stderr, "Unexpected pattern instruction: '%s'\n", p); + if(w == 0) { /* Only warn on first pass */ + (void) fprintf(stderr, + "juggle[%d]: Unexpected pattern instruction: '%c'\n", + MI_SCREEN(mi), *p); + } break; } } } - if (seen) { - if (!add_throw(sp, type, h, notation)) + if (seen) { /* end of sequence */ + if (!add_throw(sp, type, h, notation, title)) return False; + title = NULL; } } - return True; + return True; } /* @@ -494,13 +814,16 @@ program(ModeInfo *mi, const char *patn, int repeat) \\~\\\\~\\\~\~ \\\\\\\\\\\~\\ -[33134231334021] +[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ] 4 4 1 3 12 2 4 1 4 4 13 0 3 1 */ #define BOUNCEOVER 10 +#define KICKMIN 7 +#define THROWMAX 20 +/* Convert Adam notation into heights */ static void adam(jugglestruct *sp) { @@ -509,7 +832,11 @@ adam(jugglestruct *sp) if (t->status == ATCH) { int a = t->adam; t->height = 0; - for(p = t->next; a > 0 && p != sp->head; p = p->next) { + for(p = t->next; a > 0; p = p->next) { + if(p == sp->head) { + t->height = -9; /* Indicate end of processing for name() */ + return; + } if (p->status != ATCH || p->adam < 0 || p->adam>= a) { a--; } @@ -517,11 +844,51 @@ adam(jugglestruct *sp) } if(t->height > BOUNCEOVER && t->posn == ' '){ t->posn = '_'; /* high defaults can be bounced */ + } else if(t->height < 3 && t->posn == '_') { + t->posn = ' '; /* Can't bounce short throws. */ + } + if(t->height < KICKMIN && t->posn == 'k'){ + t->posn = ' '; /* Can't kick short throws */ + } + if(t->height > THROWMAX){ + t->posn = 'k'; /* Use kicks for ridiculously high throws */ } t->status = THRATCH; -#if 0 - (void) fprintf(stderr, "%d\t%d\n", t->adam, t->height); -#endif + } + } +} + +/* Discover converted heights and update the sequence title */ +static void +name(jugglestruct *sp) +{ + Trajectory *t, *p; + char buffer[BUFSIZ]; + char *b; + for(t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == THRATCH && t->name != NULL) { + b = buffer; + for(p = t; p == t || p->name == NULL; p = p->next) { + if(p == sp->head || p->height < 0) { /* end of reliable data */ + return; + } + if(p->posn == ' ') { + b += sprintf(b, " %d", p->height); + } else { + b += sprintf(b, " %c%d", p->posn, p->height); + } + if(b - buffer > 500) break; /* otherwise this could eventually + overflow. It'll be too big to + display anyway. */ + } + if(*t->name != 0) { + (void) sprintf(b, ", %s", t->name); + } + free(t->name); /* Don't need name any more, it's been converted + to pattern */ + t->name = NULL; + if(t->pattern != NULL) free(t->pattern); + t->pattern = strdup(buffer); } } } @@ -555,35 +922,61 @@ part(jugglestruct *sp) 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 '&': 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; + case 'k': posn = 'k'; t->posn = 'k'; break; + default: + (void) fprintf(stderr, "juggle: 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 */ + if (t->height == 1 && p != sp->head) { + p = p->prev; /* '1's are thrown earlier than usual */ } + + + t->action = CATCH; - INSERT_AFTER(nt, p); + ADD_ELEMENT(Trajectory, nt, p); + if(nt == NULL){ + free_juggle(sp); + return False; + } + nt->object = NULL; nt->status = ACTION; nt->action = THROW; nt->height = t->height; nt->hand = hand; nt->posn = posn; + } } return True; } +static ObjType +choose_object(void) { + ObjType o; + for (;;) { + o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES); + if(balls && o == BALL) break; + if(clubs && o == CLUB) break; + if(torches && o == TORCH) break; + if(knives && o == KNIFE) break; + if(rings && o == RING) break; + if(bballs && o == BBALLS) break; + } + return o; +} + /* Connnect up throws and catches to figure out which ball goes where. Do the same with the juggler's hands. */ @@ -595,28 +988,51 @@ lob(ModeInfo *mi) 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) { + /* Create new Object */ + ADD_ELEMENT(Object, t->object, sp->objects); + t->object->count = 1; + t->object->tracelen = 0; + t->object->active = False; + /* Initialise object's circular trace list */ + ADD_ELEMENT(Trace, t->object->trace, t->object->trace); + if (MI_NPIXELS(mi) > 2) { - t->color = 1 + NRAND(MI_NPIXELS(mi) - 2); + t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2); + } else { +#ifdef STANDALONE + t->object->color = 1; +#else + t->object->color = 0; +#endif + } + + /* Small chance of picking a random object instead of the + current theme. */ + if(NRAND(OBJMIXPROB) == 0) { + t->object->type = choose_object(); + } else { + t->object->type = sp->objtypes; + } + + /* Check to see if we need trails for this object */ + if(tail < ObjectDefs[t->object->type].mintrail) { + t->object->tail = ObjectDefs[t->object->type].mintrail; + } else { + t->object->tail = tail; } - 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) { + /* Balls can change divisions at each throw */ + t->divisions = 2 * (NRAND(2) + 1); + + /* search forward for next catch in this hand */ + for (p = t->next; t->handlink == NULL; p = p->next) { + if(p->status < ACTION || p == sp->head) return; if (p->action == CATCH) { if (t->handlink == NULL && p->hand == t->hand) { - t->handlink = p; /* next catch in this hand */ + t->handlink = p; } } } @@ -625,19 +1041,23 @@ lob(ModeInfo *mi) h = t->height - 1; /* search forward for next ball catch */ - for (p = t->next; t->balllink == NULL&& p != sp->head; p = p->next) { + for (p = t->next; t->balllink == NULL; p = p->next) { + if(p->status < ACTION || p == sp->head) { + t->handlink = NULL; + return; + } if (p->action == CATCH) { if (t->balllink == NULL && --h < 1) { /* caught */ -#if 0 + t->balllink = p; /* complete trajectory */ +# if 0 if (p->type == Full) { - /* dropped */ + (void) fprintf(stderr, "juggle[%d]: Dropped %d\n", + MI_SCREEN(mi), t->object->color); } #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; + DUP_OBJECT(p, t); /* accept catch */ + p->angle = t->angle; p->divisions = t->divisions; } } @@ -646,13 +1066,12 @@ lob(ModeInfo *mi) 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) { + for (p = t->next; t->handlink == NULL; p = p->next) { + if(p->status < ACTION || p == sp->head) return; 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); + DUP_OBJECT(p, t); /* pass object */ + p->divisions = t->divisions; t->handlink = p; } } @@ -662,56 +1081,48 @@ lob(ModeInfo *mi) } } -/* Convert hand position symbols into actual time/space coordinates */ +/* Clap when both hands are empty */ static void -positions(jugglestruct *sp) +clap(jugglestruct *sp) { - Trajectory *t; - int now = 0; + Trajectory *t, *p; 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; + if (t->status == LINKEDACTION && + t->action == CATCH && + t->type == Empty && + t->handlink != NULL && + t->handlink->height == 0) { /* Completely idle hand */ + + for (p = t->next; p != sp->head; p = p->next) { + if (p->status == LINKEDACTION && + p->action == CATCH && + p->hand != t->hand) { /* Next catch other hand */ + if(p->type == Empty && + p->handlink != NULL && + p->handlink->height == 0) { /* Also completely idle */ + + t->handlink->posn = '^'; /* Move first hand's empty throw */ + p->posn = '^'; /* to meet second hand's empty + catch */ - /* 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 */ + } + break; /* Only need first 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; } } } +#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d) -/* Private physics functions */ - +/* Compute single spline from x0 with velocity dx0 at time t0 to x1 + with velocity dx1 at time t1 */ static Spline -makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1) +makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1) { Spline s; double a, b, c, d; - int x10; + double x10; double t10; x10 = x1 - x0; @@ -727,14 +1138,17 @@ makeSpline(int x0, double dx0, int t0, int x1, double dx1, int t1) return s; } +/* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at + time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with + velocity dx2 at time t2. The arrival and departure velocities at + x1, t1 must be the same. */ static double makeSplinePair(Spline *s1, Spline *s2, - int x0, double dx0, int t0, - int x1, int t1, - int x2, double dx2, int t2) + double x0, double dx0, int t0, + double x1, int t1, + double x2, double dx2, int t2) { - int x10, x21; - double t21, t10, t20, dx1; + double x10, x21, t21, t10, t20, dx1; x10 = x1 - x0; x21 = x2 - x1; t21 = t2 - t1; @@ -748,311 +1162,533 @@ makeSplinePair(Spline *s1, Spline *s2, return dx1; } -/* Turn abstract timings into physically appropriate ball trajectories. */ -static Bool -projectile(jugglestruct *sp) +/* Compute a Ballistic path in a pair of degenerate splines. sx goes + from x at time t at constant velocity dx. sy goes from y at time t + with velocity dy and constant acceleration g. */ +static void +makeParabola(Trajectory *n, + double x, double dx, double y, double dy, double g) { - 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; + double t = (double)n->start; + n->xp.a = 0; + n->xp.b = 0; + n->xp.c = dx; + n->xp.d = -dx*t + x; + n->yp.a = 0; + n->yp.b = g/2; + n->yp.c = -g*t + dy; + n->yp.d = g/2*t*t - dy*t + y; +} - /* 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; - } +/* Make juggler wander around the screen */ +static double wander(jugglestruct *sp, unsigned long time) +{ + Wander *w = NULL; + for (w = sp->wander->next; w != sp->wander; w = w->next) { + if (w->finish < sp->time) { /* expired */ + Wander *ww = w; + w = w->prev; + REMOVE(ww); + } else if(w->finish > time) { + break; } } - return True; + if(w == sp->wander) { /* Need a new one */ + ADD_ELEMENT(Wander, w, sp->wander->prev); + if(w == NULL) { /* Memory problem */ + return 0.0; + } + w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL); + if(time == 0) { + w->x = 0; + } else { + w->x = w->prev->x * 0.9 + NRAND(40) - 20; + } + w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish); + } + return CUBIC(w->s, time); } -/* Turn abstract hand motions into cubic splines. */ +#define SX 25 /* Shoulder Width */ + +/* Convert hand position symbols into actual time/space coordinates */ static void -hands(jugglestruct *sp) +positions(jugglestruct *sp) { - Trajectory *t, *u, *v; + Trajectory *t; + unsigned long now = sp->time; /* Make sure we're not lost in the past */ for (t = sp->head->next; t != sp->head; t = t->next) { - /* no throw => no velocity */ - if (t->status != BPREDICTOR) { - continue; - } + if (t->status >= PTHRATCH) { + now = t->start; + } else if (t->status == ACTION || t->status == LINKEDACTION) { + /* Allow ACTIONs to be annotated, but we won't mark them ready + for the next stage */ - u = t->handlink; - if (u == NULL) { /* no next catch */ - continue; - } - v = u->handlink; - if (v == NULL) { /* no next throw */ - continue; - } + double xo = 0, yo; + double sx = SX; + double pose = SX/2; - /* double spline takes hand from throw, thru catch, to - next throw */ + /* time */ + if (t->action == CATCH) { /* Throw-to-catch */ + if (t->type == Empty) { + now += (int) THROW_NULL_INTERVAL; /* failed catch is short */ + } else { /* successful catch */ + now += (int)(THROW_CATCH_INTERVAL); + } + } else { /* Catch-to-throw */ + if(t->object != NULL) { + now += (int) (CATCH_THROW_INTERVAL * + ObjectDefs[t->object->type].weight); + } else { + now += (int) (CATCH_THROW_INTERVAL); + } + } - t->finish = u->start; - t->status = PREDICTOR; + if(t->start == 0) + t->start = now; + else /* Concatenated performances may need clock resync */ + now = t->start; - u->finish = v->start; - u->status = PREDICTOR; + t->cx = wander(sp, t->start); - (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); + /* space */ + yo = 90; - t->status = PREDICTOR; - } -} + /* Add room for the handle */ + if(t->action == CATCH && t->object != NULL) + yo -= ObjectDefs[t->object->type].handle; -/* 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; + switch (t->posn) { + case '-': xo = sx - pose; break; + case '_': + case 'k': + case '+': xo = sx + pose; break; + case '~': + case '=': xo = - sx - pose; yo += pose; break; + case '^': xo = 0; yo += pose*2; break; /* clap */ + default: + (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn); + break; + } - 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; + t->angle = (((t->hand == LEFT) ^ + (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))? + -1 : 1) * M_PI/2; + + t->x = t->cx + ((t->hand == LEFT) ? xo : -xo); + t->y = yo; + + /* Only mark complete if it was already linked */ + if(t->status == LINKEDACTION) { + t->status = PTHRATCH; + } + } } } -/* 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; +/* Private physics functions */ - 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; +/* Compute the spin-rate for a trajectory. Different types of throw + (eg, regular thows, bounces, kicks, etc) have different spin + requirements. + + type = type of object + h = trajectory of throwing hand (throws), or next throwing hand (catches) + old = earlier spin to consider + dt = time span of this trajectory + height = height of ball throw or 0 if based on old spin + turns = full club turns required during this operation + togo = partial club turns required to match hands +*/ +static double +spinrate(ObjType type, Trajectory *h, double old, double dt, + int height, int turns, double togo) +{ + const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1; + + if(ObjectDefs[type].handle != 0) { /* Clubs */ + return (dir * turns * 2 * M_PI + togo) / dt; + } else if(height == 0) { /* Balls already spinning */ + return old/2; + } else { /* Balls */ + return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt; } - 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); +/* compute the angle at the end of a spinning trajectory */ +static double +end_spin(Trajectory *t) +{ + return t->angle + t->spin * (t->finish - t->start); } +/* Sets the initial angle of the catch following hand movement t to + the final angle of the throw n. Also sets the angle of the + subsequent throw to the same angle plus half a turn. */ +static void +match_spins_on_catch(Trajectory *t, Trajectory *n) +{ + if(ObjectDefs[t->balllink->object->type].handle == 0) { + t->balllink->angle = end_spin(n); + if(t->balllink->handlink != NULL) { + t->balllink->handlink->angle = t->balllink->angle + M_PI; + } + } +} + +static double +find_bounce(jugglestruct *sp, + double yo, double yf, double yc, double tc, double cor) +{ + double tb, i, dy = 0; + const double 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.0001; i/=2){ + double dt, yt; + if(tb == 0){ + (void) fprintf(stderr, "juggle: bounce div by zero!\n"); + break; + } + dy = (yf - yo)/tb + sp->Gr/2*tb; + dt = tc - tb; + yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf; + if(yt < yc + e){ + tb-=i; + }else if(yt > yc - e){ + tb+=i; + }else{ + break; + } + } + if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */ + tb = -1; + } + return tb; +} + +static Trajectory* +new_predictor(const Trajectory *t, int start, int finish, double angle) +{ + Trajectory *n; + ADD_ELEMENT(Trajectory, n, t->prev); + if(n == NULL){ + return NULL; + } + DUP_OBJECT(n, t); + n->divisions = t->divisions; + n->type = Ball; + n->status = PREDICTOR; + + n->start = start; + n->finish = finish; + n->angle = angle; + return n; +} + +/* Turn abstract timings into physically appropriate object trajectories. */ +static Bool +projectile(jugglestruct *sp) +{ + Trajectory *t; + const int yf = 0; /* Floor height */ + + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status != PTHRATCH || t->action != THROW) { + continue; + } else if (t->balllink == NULL) { /* Zero Throw */ + t->status = BPREDICTOR; + } else if (t->balllink->handlink == NULL) { /* Incomplete */ + return True; + } else if(t->balllink == t->handlink) { + /* '2' height - hold on to ball. Don't need to consider + flourishes, 'hands' will do that automatically anyway */ + + t->type = Full; + /* Zero spin to avoid wrist injuries */ + t->spin = 0; + match_spins_on_catch(t, t); + t->dx = t->dy = 0; + t->status = BPREDICTOR; + continue; + } else { + if (t->posn == '_') { /* Bounce once */ + + const int tb = t->start + + find_bounce(sp, t->y, (double) yf, t->balllink->y, + (double) (t->balllink->start - t->start), + ObjectDefs[t->object->type].cor); + + if(tb < t->start) { /* bounce too hard */ + t->posn = '+'; /* Use regular throw */ + } else { + Trajectory *n; /* First (throw) trajectory. */ + double dt; /* Time span of a trajectory */ + double dy; /* Distance span of a follow-on trajectory. + First trajectory uses t->dy */ + /* dx is constant across both trajectories */ + t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start); + + { /* ball follows parabola down */ + n = new_predictor(t, t->start, tb, t->angle); + if(n == NULL) return False; + dt = n->finish - n->start; + /* Ball rate 4, no flight or matching club turns */ + n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0); + t->dy = (yf - t->y)/dt - sp->Gr/2*dt; + makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr); + } + + { /* ball follows parabola up */ + Trajectory *m = new_predictor(t, n->finish, t->balllink->start, + end_spin(n)); + if(m == NULL) return False; + dt = m->finish - m->start; + /* Use previous ball rate, no flight club turns */ + m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0, + t->balllink->angle - m->angle); + match_spins_on_catch(t, m); + dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt; + makeParabola(m, t->balllink->x - t->dx * dt, + t->dx, (double) yf, dy, sp->Gr); + } + + t->status = BPREDICTOR; + continue; + } + } else if (t->posn == 'k') { /* Drop & Kick */ + Trajectory *n; /* First (drop) trajectory. */ + Trajectory *o; /* Second (rest) trajectory */ + Trajectory *m; /* Third (kick) trajectory */ + const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */ + const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */ + double dt, dy; + + { /* Fall to ground */ + n = new_predictor(t, t->start, td, t->angle); + if(n == NULL) return False; + dt = n->finish - n->start; + /* Ball spin rate 4, no flight club turns */ + n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, + t->balllink->angle - n->angle); + t->dx = (t->balllink->x - t->x) / dt; + t->dy = (yf - t->y)/dt - sp->Gr/2*dt; + makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr); + } + + { /* Rest on ground */ + o = new_predictor(t, n->finish, tk, end_spin(n)); + if(o == NULL) return False; + o->spin = 0; + makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0); + } + + /* Kick up */ + { + m = new_predictor(t, o->finish, t->balllink->start, end_spin(o)); + if(m == NULL) return False; + dt = m->finish - m->start; + /* Match receiving hand, ball rate 4, one flight club turn */ + m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt, + 4, 1, t->balllink->angle - m->angle); + match_spins_on_catch(t, m); + dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt; + makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr); + } + + t->status = BPREDICTOR; + continue; + } + + /* Regular flight, no bounce */ + { /* ball follows parabola */ + double dt; + Trajectory *n = new_predictor(t, t->start, + t->balllink->start, t->angle); + if(n == NULL) return False; + dt = t->balllink->start - t->start; + /* Regular spin */ + n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2, + t->balllink->angle - n->angle); + match_spins_on_catch(t, n); + t->dx = (t->balllink->x - t->x) / dt; + t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt; + makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr); + } + + 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; + + + /* FIXME: These adjustments leave a small glitch when alternating + balls and clubs. Just hope no-one notices. :-) */ + + /* make sure empty hand spin matches the thrown object in case it + had a handle */ + + t->spin = ((t->hand == LEFT)? -1 : 1 ) * + fabs((u->angle - t->angle)/(u->start - t->start)); + u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) * + fabs((v->angle - u->angle)/(v->start - u->start)); + + (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(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s, + int z) +{ + double r, h2, t; + double x = p->x - s->x; + double y = p->y - s->y; + h2 = x*x + y*y + z*z; + if (h2 > 4 * armlength * armlength) { + t = armlength/sqrt(h2); + e->x = t*x + s->x; + e->y = t*y + s->y; + h->x = 2 * t * x + s->x; + h->y = 2 * t * y + s->y; + } else { + r = sqrt((double)(x*x + z*z)); + t = sqrt(4 * armlength * armlength / h2 - 1); + e->x = x*(1 + y*t/r)/2 + s->x; + e->y = (y - r*t)/2 + s->y; + h->x = x + s->x; + h->y = y + s->y; + } +} + + +/* NOTE: returned x, y adjusted for arm reach */ +static void +reach_arm(ModeInfo * mi, Hand side, DXPoint *p) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + DXPoint h, e; + find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25); + *p = sp->arm[1][side][HAND] = h; + sp->arm[1][side][ELBOW] = e; +} + +#if DEBUG /* 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); + case ATCH: + (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam); + break; + case THRATCH: + (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height, + t->pattern == NULL?"":t->pattern); 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); + if (t->action == CATCH) + (void) fprintf(stderr, "%p A %c%cC\n", + (void*)t, t->posn, + t->hand ? 'R' : 'L'); + else + (void) fprintf(stderr, "%p A %c%c%c%d\n", + (void*)t, t->posn, + t->hand ? 'R' : 'L', + (t->action == THROW)?'T':'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); + (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n", + (void*)t, t->posn, + t->hand?'R':'L', + (t->action == THROW)?'T':(t->action == CATCH?'C':'N'), + t->height, t->object == NULL?0:t->object->color, + (void*)t->handlink, (void*)t->balllink); 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); + (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n", + (void*)t, t->posn, + t->hand?'R':'L', + (t->action == THROW)?'T':(t->action == CATCH?'C':'N'), + t->height, t->type, t->object == NULL?0:t->object->color, + t->start, t->finish); + break; + case BPREDICTOR: + (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n", + (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f', + t->object == NULL?0:t->object->color, + t->start, t->finish, t->yp.c); 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); + (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n", + (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f', + t->object == NULL?0:t->object->color, + t->start, t->finish, t->yp.c); break; default: - (void) fprintf(stderr, "status %d not implemented\n", t->status); + (void) fprintf(stderr, "%p: status %d not implemented\n", + (void*)t, t->status); break; } } + (void) fprintf(stderr, "---\n"); } #endif @@ -1078,10 +1714,12 @@ static int get_num_balls(const char *j) extern "C" { #endif -static int compare_num_balls(const void *p1, const void *p2) +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); + int i, j; + i = get_num_balls(((patternstruct*)p1)->pattern); + j = get_num_balls(((patternstruct*)p2)->pattern); if (i > j) { return (1); } else if (i < j) { @@ -1095,453 +1733,1080 @@ static int compare_num_balls(const void *p1, const void *p2) } #endif -/* Public functions */ -void -release_juggle(ModeInfo * mi) +/************************************************************************** + * Rendering Functions * + * * + **************************************************************************/ + +static void +show_arms(ModeInfo * mi, unsigned long color) { - if (juggles != NULL) { - int screen; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + unsigned int i, j; + Hand side; + XPoint a[XtNumber(sp->arm[0][0])]; + if(color == MI_BLACK_PIXEL(mi)) { + j = 0; + } else { + j = 1; + } + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), + ARMWIDTH, LineSolid, CapRound, JoinRound); + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) { + /* Translate into device coords */ + for(i = 0; i < XtNumber(a); i++) { + a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale); + a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale); + if(j == 1) + sp->arm[0][side][i] = sp->arm[1][side][i]; + } + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + a, XtNumber(a), CoordModeOrigin); + } +} - for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) - free_juggle(&juggles[screen]); - (void) free((void *) juggles); - juggles = (jugglestruct *) NULL; +static void +show_figure(ModeInfo * mi, unsigned long color, Bool init) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + XPoint p[7]; + unsigned int i; + + /* +-----+ 9 + | 6 | + 10 +--+--+ + 2 +---+---+ 3 + \ 5 / + \ / + \ / + 1 + + / \ + / \ + 0 +-----+ 4 + | | + | | + | | + 7 + + 8 + */ + + static const XPoint figure[] = { + { 15, 70}, /* 0 Left Hip */ + { 0, 90}, /* 1 Waist */ + { SX, 130}, /* 2 Left Shoulder */ + {-SX, 130}, /* 3 Right Shoulder */ + {-15, 70}, /* 4 Right Hip */ + { 0, 130}, /* 5 Neck */ + { 0, 140}, /* 6 Chin */ + { SX, 0}, /* 7 Left Foot */ + {-SX, 0}, /* 8 Right Foot */ + {-17, 174}, /* 9 Head1 */ + { 17, 140}, /* 10 Head2 */ + }; + XPoint a[XtNumber(figure)]; + + /* Translate into device coords */ + for(i = 0; i < XtNumber(figure); i++) { + a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale); + a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale); + } + + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), + ARMWIDTH, LineSolid, CapRound, JoinRound); + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + + i = 0; /* Body */ + p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2]; + p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4]; + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, CoordModeOrigin); + + i = 0; /* Legs */ + p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8]; + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, CoordModeOrigin); + + i = 0; /* Neck */ + p[i++] = a[5]; p[i++] = a[6]; + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, CoordModeOrigin); + + /* Head */ + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + a[9].x, a[9].y, + a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360); + sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x; + sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x; + if(init) { + /* Initialise arms */ + unsigned int i; + for(i = 0; i < 2; i++){ + sp->arm[i][LEFT][SHOULDER].y = figure[2].y; + sp->arm[i][LEFT][ELBOW].x = figure[2].x; + sp->arm[i][LEFT][ELBOW].y = figure[1].y; + sp->arm[i][LEFT][HAND].x = figure[0].x; + sp->arm[i][LEFT][HAND].y = figure[1].y; + sp->arm[i][RIGHT][SHOULDER].y = figure[3].y; + sp->arm[i][RIGHT][ELBOW].x = figure[3].x; + sp->arm[i][RIGHT][ELBOW].y = figure[1].y; + sp->arm[i][RIGHT][HAND].x = figure[4].x; + sp->arm[i][RIGHT][HAND].y = figure[1].y; + } + } +} + +static void +show_ball(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + int offset = (int)(s->angle*64*180/M_PI); + short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale); + short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale); + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) { + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + 0, 23040); + } else if (s->divisions == 4) { /* 90 degree divisions */ + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + offset % 23040, 5760); + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 11520) % 23040, 5760); + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 5760) % 23040, 5760); + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 17280) % 23040, 5760); + } else if (s->divisions == 2) { /* 180 degree divisions */ + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + offset % 23040, 11520); + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + x - BALLRADIUS, y - BALLRADIUS, + 2*BALLRADIUS, 2*BALLRADIUS, + (offset + 11520) % 23040, 11520); + } else { + (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n", + MI_SCREEN(mi), s->divisions); + } +} + +static void +show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + XPoint p[4]; + const double sa = sin(s->angle); + const double ca = cos(s->angle); + unsigned int i; + + /* 6 6 + +-+ + / \ + 4 +-----+ 7 + ////////\ + 3 +---------+ 8 + 2 +---------+ 9 + |///////| + 1 +-------+ 10 + | | + | | + | | + | | + | | + | | + +-+ + 0 11 */ + + static const XPoint club[] = { + {-24, 2}, /* 0 */ + {-10, 3}, /* 1 */ + { 1, 6}, /* 2 */ + { 8, 6}, /* 3 */ + { 14, 4}, /* 4 */ + { 16, 3}, /* 5 */ + { 16,-3}, /* 6 */ + { 14,-4}, /* 7 */ + { 8,-6}, /* 8 */ + { 1,-6}, /* 9 */ + {-10,-3}, /* 10 */ + {-24,-2}, /* 11 */ + {-24, 2}, /* 0 close boundary */ + }; + XPoint a[XtNumber(club)]; + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + /* Translate and fake perspective */ + for(i = 0; i < XtNumber(club); i++) { + a[i].x = (short)(MI_WIDTH(mi)/2 + + (s->x + club[i].x*PERSPEC*sa)*sp->scale - + club[i].y*sqrt(sp->scale)*ca); + a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale + + club[i].y*sa*sqrt(sp->scale)); + } + + if(color != MI_BLACK_PIXEL(mi)) { + /* Outline in black */ + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi)); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2, + LineSolid, CapRound, JoinRound); + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + a, XtNumber(a), CoordModeOrigin); } - if (patternindex != NULL) { - (void) free((void *) patternindex); - patternindex = (PatternIndex *) NULL; + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + + /* Don't be tempted to optimize erase by drawing all the black in + one X operation. It must use the same ops as the colours to + guarantee a clean erase. */ + + i = 0; /* Colored stripes */ + p[i++] = a[1]; p[i++] = a[2]; + p[i++] = a[9]; p[i++] = a[10]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); + i = 0; + p[i++] = a[3]; p[i++] = a[4]; + p[i++] = a[7]; p[i++] = a[8]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); + + if(color != MI_BLACK_PIXEL(mi)) { + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); } + + i = 0; /* White center band */ + p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); + + i = 0; /* White handle */ + p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); + + i = 0; /* White tip */ + p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); } -void -init_juggle(ModeInfo * mi) +#if 0 +static void +show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s) { - jugglestruct *sp; - int i; - XPoint figure1[FIGURE1]; - XSegment figure2[FIGURE2]; - if (pattern != NULL && *pattern == '.') { - pattern = NULL; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + XPoint p[6]; + const double sa = sin(s->angle); + const double ca = cos(s->angle); + unsigned int i; + + /* 4 5 + +-+ + / \ + 3 +-----+ 6 + ////////\ + 2 +/////////+ 7 + |///////| + 1 +-------+ 8 + | | + | | + | | + | | + | | + | | + +-+ + 0 9 */ + + static const XPoint club[] = { + {-24, 2}, /* 0 */ + { -9, 3}, /* 1 */ + { 5, 6}, /* 2 */ + { 11, 4}, /* 3 */ + { 16, 3}, /* 4 */ + { 16,-3}, /* 5 */ + { 11,-4}, /* 6 */ + { 5,-6}, /* 7 */ + { -9,-3}, /* 8 */ + {-24,-2}, /* 9 */ + {-24, 2}, /* 0 close boundary */ + }; + XPoint a[XtNumber(club)]; + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + /* Translate and fake perspective */ + for(i = 0; i < XtNumber(club); i++) { + a[i].x = (short)(MI_WIDTH(mi)/2 + + (s->x + club[i].x*PERSPEC*sa)*sp->scale - + club[i].y*sqrt(sp->scale)*ca); + a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale + + club[i].y*sa*sqrt(sp->scale)); } - if (pattern == NULL && patternindex == NULL) { - /* pattern list needs indexing */ - 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(color != MI_BLACK_PIXEL(mi)) { + /* Outline in black */ + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi)); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2, + LineSolid, CapRound, JoinRound); + XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + a, XtNumber(a), CoordModeOrigin); } - if (juggles == NULL) { /* allocate jugglestruct */ - if ((juggles = (jugglestruct *) calloc(MI_NUM_SCREENS(mi), - sizeof (jugglestruct))) == NULL) { - release_juggle(mi); - return; - } + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + + /* Don't be tempted to optimize erase by drawing all the black in + one X operation. It must use the same ops as the colours to + guarantee a clean erase. */ + + i = 0; /* Coloured center band */ + p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; + p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); + + if(color != MI_BLACK_PIXEL(mi)) { + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); } - sp = &juggles[MI_SCREEN(mi)]; - sp->count = 0; + i = 0; /* White handle */ + p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); - 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; + i = 0; /* White tip */ + p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); +} #endif + +static void +show_torch(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + XPoint head, tail, last; + DXPoint dhead, dlast; + const double sa = sin(s->angle); + const double ca = cos(s->angle); + + const double TailLen = -24; + const double HeadLen = 16; + const short Width = (short)(5 * sqrt(sp->scale)); + + /* + +///+ head + last | + | + | + | + | + + tail + */ + + dhead.x = s->x + HeadLen * PERSPEC * sa; + dhead.y = s->y - HeadLen * ca; + + if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */ + dlast = s->dlast; + } else { /* Store 'last' so we can use it later when s->prev has + gone */ + if(s->prev != s->next) { + dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle); + dlast.y = s->prev->y - HeadLen * cos(s->prev->angle); + } else { + dlast = dhead; + } + s->dlast = dlast; } - 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; + /* Avoid wrapping (after last is stored) */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale); + head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale); + + last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale); + last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale); + + tail.x = (short)(MI_WIDTH(mi)/2 + + (s->x + TailLen * PERSPEC * sa)*sp->scale ); + tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale ); + + if(color != MI_BLACK_PIXEL(mi)) { + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi)); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), + Width, LineSolid, CapRound, JoinRound); + XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + head.x, head.y, tail.x, tail.y); } - /* 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; + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), + Width * 2, LineSolid, CapRound, JoinRound); + + XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + head.x, head.y, last.x, last.y); + +} + +static void +show_knife(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + unsigned int i; + const double sa = sin(s->angle); + const double ca = cos(s->angle); + + /* + 2 + + |+ 3 + || + 1 +++ 5 + |4| + | | + + 0 + */ + static const XPoint knife[] = { + {-24, 0}, /* 0 */ + { -5,-3}, /* 1 */ + { 16,-3}, /* 2 */ + { 12, 0}, /* 3 */ + { -5, 0}, /* 4 */ + { -5, 3}, /* 5 */ + }; + XPoint a[XtNumber(knife)], p[5]; + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + /* Translate and fake perspective */ + for(i = 0; i < XtNumber(knife); i++) { + a[i].x = (short)(MI_WIDTH(mi)/2 + + (s->x + knife[i].x*PERSPEC*sa)*sp->scale - + knife[i].y*sqrt(sp->scale)*ca*PERSPEC); + a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale + + knife[i].y*sa*sqrt(sp->scale)); } - /* 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; - } + + /* Handle */ + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)), + LineSolid, CapRound, JoinRound); + XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + a[0].x, a[0].y, a[4].x, a[4].y); + + /* Blade */ + if(color != MI_BLACK_PIXEL(mi)) { + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); } + i = 0; + p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5]; + XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + p, i, Convex, CoordModeOrigin); +} - /* Clear the background. */ - MI_CLEARWINDOW(mi); +static void +show_ring(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale); + short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale); + double radius = 15 * sp->scale; + short thickness = (short)(8 * sqrt(sp->scale)); + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), + thickness, LineSolid, CapRound, JoinRound); + + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius*PERSPEC), (short)(y - radius), + (short)(2*radius*PERSPEC), (short)(2*radius), + 0, 23040); +} - draw_figure(mi); - /* record start time */ - sp->begintime = time(NULL); +static void +show_bball(ModeInfo *mi, unsigned long color, Trace *s) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale); + short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale); + double radius = 12 * sp->scale; + int offset = (int)(s->angle*64*180/M_PI); + int holesize = (int)(3.0*sqrt(sp->scale)); + + /* Avoid wrapping */ + if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return; + + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi)); + XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius), (short)(y - radius), + (short)(2*radius), (short)(2*radius), + 0, 23040); + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color); + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2, + LineSolid, CapRound, JoinRound); + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius), (short)(y - radius), + (short)(2*radius), (short)(2*radius), + 0, 23040); + + /* Draw finger holes */ + XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize, + LineSolid, CapRound, JoinRound); + + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius*0.5), (short)(y - radius*0.5), + (short)(2*radius*0.5), (short)(2*radius*0.5), + (offset + 960) % 23040, 0); + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius*0.7), (short)(y - radius*0.7), + (short)(2*radius*0.7), (short)(2*radius*0.7), + (offset + 1920) % 23040, 0); + XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + (short)(x - radius*0.7), (short)(y - radius*0.7), + (short)(2*radius*0.7), (short)(2*radius*0.7), + offset % 23040, 0); +} - free_pattern(sp); +/************************************************************************** + * Public Functions * + * * + **************************************************************************/ - /* create circular list */ - INSERT_AFTER_TOP(sp->head, sp->head); - /* generate pattern */ - if (pattern == NULL) { +void +release_juggle(ModeInfo * mi) +{ + if (juggles != NULL) { + int screen; + + for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) + free_juggle(&juggles[screen]); + free(juggles); + juggles = (jugglestruct *) NULL; + } + if (mode_font!=None) { + XFreeFontInfo(NULL,mode_font,1); + mode_font = None; + } +} + +/* FIXME: refill_juggle currently just appends new throws to the + * programme. This is fine if the programme is empty, but if there + * are still some trajectories left then it really should take these + * into account */ + +static void +refill_juggle(ModeInfo * mi) +{ + jugglestruct *sp = NULL; + int i; + + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; + + /* generate pattern */ + if (pattern == NULL) { #define MAXPAT 10 -#define MAXREPEAT 30 +#define MAXREPEAT 300 #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; + int count = 0; + while (count < MI_CYCLES(mi)) { + char buf[MAXPAT * 3 + 3], *b = buf; + int maxseen = 0; + int l = NRAND(MAXPAT) + 1; + int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1; + + { /* vary number of balls */ + int new_balls = sp->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 */ } - 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)) + if (new_balls < patternindex.minballs) { + new_balls += 2; + } + if (new_balls > patternindex.maxballs) { + new_balls -= 2; + } + if (new_balls < sp->num_balls) { + if (!program(mi, "[*]", NULL, 1)) /* lose ball */ 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 */ - } + } + sp->num_balls = new_balls; + } - *b++ = n + '0'; - *b++ = ' '; + count += t; + if (NRAND(2) && patternindex.index[sp->num_balls].number) { + /* Pick from PortFolio */ + int p = patternindex.index[sp->num_balls].start + + NRAND(patternindex.index[sp->num_balls].number); + if (!program(mi, portfolio[p].pattern, portfolio[p].name, 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(sp->num_balls + 1); + n = NRAND(sp->num_balls + 1); + } while(m >= n); + if (n == sp->num_balls) { + maxseen = 1; } - *b++ = ']'; - *b = '\0'; - if (maxseen) { - if (!program(mi, buf, t)) - return; + switch(NRAND(5 + POSITION_BIAS)){ + case 0: /* Outside throw */ + *b++ = '+'; break; + case 1: /* Cross throw */ + *b++ = '='; break; + case 2: /* Cross catch */ + *b++ = '&'; break; + case 3: /* Cross throw and catch */ + *b++ = 'x'; break; + case 4: /* Bounce */ + *b++ = '_'; break; + default: + break; /* Inside throw (default) */ } + + *b++ = n + '0'; + *b++ = ' '; + } + *b++ = ']'; + *b = '\0'; + if (maxseen) { + if (!program(mi, buf, NULL, t)) + return; } } - } else { /* pattern supplied in height or 'a' notation */ - if (!program(mi, pattern, MI_CYCLES(mi))) - return; } + } else { /* pattern supplied in height or 'a' notation */ + if (!program(mi, pattern, NULL, MI_CYCLES(mi))) + return; + } - adam(sp); + adam(sp); - if (!part(sp)) - return; + name(sp); - lob(mi); + if (!part(sp)) + return; - positions(sp); + lob(mi); - if (!projectile(sp)) - return; + clap(sp); - hands(sp); + positions(sp); -#ifdef OLDDEBUG - dump(sp); + if (!projectile(sp)) { + free_juggle(sp); + return; + } + + hands(sp); +#ifdef DEBUG + if(MI_IS_DEBUG(mi)) dump(sp); #endif } +void +change_juggle(ModeInfo * mi) +{ + jugglestruct *sp = NULL; + Trajectory *t; -#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d) + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; -#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 + /* Strip pending trajectories */ + for (t = sp->head->next; t != sp->head; t = t->next) { + if(t->start > sp->time || t->finish < sp->time) { + Trajectory *n = t; + t=t->prev; + trajectory_destroy(n); + } + } -#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 + /* Pick the current object theme */ + sp->objtypes = choose_object(); -static void -draw_juggle_ball(ModeInfo *mi, unsigned long color, int x, int y, double degree_offset, int divisions) + refill_juggle(mi); + + /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we + don't all those special effects. */ + XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi)); + + show_figure(mi, MI_WHITE_PIXEL(mi), True); + +} + +#ifdef STANDALONE +/* Used by xscreensaver. xlock just uses init_juggle */ +void +reshape_juggle(ModeInfo * mi, int width, int height) { - 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); + init_juggle(mi); } +#endif void -draw_juggle(ModeInfo * mi) +init_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; + jugglestruct *sp; + int i; + + if (only && *only && strcmp(only, " ")) { + balls = clubs = torches = knives = rings = bballs = False; + if (!strcasecmp (only, "balls")) balls = True; + else if (!strcasecmp (only, "clubs")) clubs = True; + else if (!strcasecmp (only, "torches")) torches = True; + else if (!strcasecmp (only, "knives")) knives = True; + else if (!strcasecmp (only, "rings")) rings = True; + else if (!strcasecmp (only, "bballs")) bballs = True; + else { + (void) fprintf (stderr, + "Juggle: -only must be one of: balls, clubs, torches, knives,\n" + "\t rings, or bballs (not \"%s\")\n", only); +#ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */ + exit (1); +#endif + } + } + + if (pattern != NULL && *pattern == '.') { + pattern = NULL; + } + if (pattern == NULL && patternindex.maxballs == 0) { + /* pattern list needs indexing */ + int nelements = XtNumber(portfolio); + int numpat = 0; + + /* sort according to number of balls */ + qsort((void*)portfolio, nelements, + sizeof(portfolio[1]), compare_num_balls); + + /* last pattern has most balls */ + patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern); + /* run through sorted list, indexing start of each group + and number in group */ + patternindex.maxballs = 1; + for (i = 0; i < nelements; i++) { + int b = get_num_balls(portfolio[i].pattern); + if (b > patternindex.maxballs) { + patternindex.index[patternindex.maxballs].number = numpat; + if(numpat == 0) patternindex.minballs = b; + patternindex.maxballs = b; + numpat = 1; + patternindex.index[patternindex.maxballs].start = i; + } else { + numpat++; + } + } + patternindex.index[patternindex.maxballs].number = numpat; + } + + /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may + only be resizing and then we won't all those special effects. */ + XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi)); + + if (juggles == NULL) { /* First-time initialisation */ + + /* allocate jugglestruct */ + if ((juggles = + (jugglestruct *)calloc(MI_NUM_SCREENS(mi), + sizeof (jugglestruct))) == NULL) { + release_juggle(mi); + return; + } + sp = &juggles[MI_SCREEN(mi)]; - if (sp->trace == NULL) - return; - MI_IS_DRAWN(mi) = True; + sp->count = ABS(MI_COUNT(mi)); + if (sp->count == 0) + sp->count = 200; - draw_figure(mi); + /* record start time */ + sp->begintime = time(NULL); + if(patternindex.maxballs > 0) { + sp->num_balls = patternindex.minballs + + NRAND(patternindex.maxballs - patternindex.minballs); + } - { - struct timeval tv; -# ifdef GETTIMEOFDAY_TWO_ARGS - struct timezone tzp; - gettimeofday(&tv, &tzp); -# else - gettimeofday(&tv); -# endif + show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers + information about the juggler's + proportions */ + + /* "7" should be about three times the height of the juggler's + shoulders */ + sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y, + 7 * THROW_CATCH_INTERVAL); + + if(!balls && !clubs && !torches && !knives && !rings && !bballs) + balls = True; /* Have to juggle something! */ + + /* create circular trajectory list */ + ADD_ELEMENT(Trajectory, sp->head, sp->head); + if(sp->head == NULL){ + free_juggle(sp); + return; + } + + /* create circular object list */ + ADD_ELEMENT(Object, sp->objects, sp->objects); + if(sp->objects == NULL){ + free_juggle(sp); + return; + } + + /* create circular wander list */ + ADD_ELEMENT(Wander, sp->wander, sp->wander); + if(sp->wander == NULL){ + free_juggle(sp); + return; + } + (void)wander(sp, 0); /* Initialize wander */ + + sp->pattern = strdup(""); /* Initialise saved pattern with + free-able memory */ + + /* Set up programme */ + change_juggle(mi); + + } + + /* Only put things here that won't interrupt the programme during + a window resize */ + + sp = &juggles[MI_SCREEN(mi)]; + + /* Use MIN so that users can resize in interesting ways, eg + narrow windows for tall patterns, etc */ + sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0); - sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000); + if(describe && mode_font == None) { /* Check to see if there's room to describe patterns. */ + mode_font = XQueryFont(MI_DISPLAY(mi), XGContextFromGC(MI_GC(mi))); + } +} + +void +draw_juggle(ModeInfo * mi) +{ + Trajectory *traj = NULL; + Object *o = NULL; + unsigned long future = 0; + jugglestruct *sp = NULL; + char *pattern = NULL; + double cx; + + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; + + MI_IS_DRAWN(mi) = True; + + /* Update timer */ + if (real) { + struct timeval tv; + (void)gettimeofday(&tv, NULL); + sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000); + } else { + sp->time += MI_DELAY(mi) / 1000; + } + + /* First pass: Move arms and strip out expired elements */ + for (traj = sp->head->next; traj != sp->head; traj = traj->next) { + if (traj->status != PREDICTOR) { + /* Skip any elements that need further processing */ + /* We could remove them, but there shoudn't be many and they + would be needed if we ever got the pattern refiller + working */ + continue; + } + if (traj->start > future) { /* Lookahead to the end of the show */ + future = traj->start; } - 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 */ + + /* Look for pattern name */ + if(traj->pattern != NULL) { + pattern=traj->pattern; } - 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; - } + if (traj->type == Empty || traj->type == Full) { + /* Only interested in hands on this pass */ + double angle = traj->angle + traj->spin * (sp->time - traj->start); + double xd = 0, yd = 0; + DXPoint p; + + /* Find the catching offset */ + if(traj->object != NULL) { + if(ObjectDefs[traj->object->type].handle > 0) { + /* Handles Need to be oriented */ + xd = ObjectDefs[traj->object->type].handle * + PERSPEC * sin(angle); + yd = ObjectDefs[traj->object->type].handle * + cos(angle); } else { - ERASE_BALL(traj->x, traj->y); + /* Balls are always caught at the bottom */ + xd = 0; + yd = -4; } - 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); + p.x = (CUBIC(traj->xp, sp->time) - xd); + p.y = (CUBIC(traj->yp, sp->time) + yd); + reach_arm(mi, traj->hand, &p); + + /* Store updated hand position */ + traj->x = p.x + xd; + traj->y = p.y - yd; + } + if (traj->type == Ball || traj->type == Full) { + /* Only interested in objects on this pass */ + double x, y; + Trace *s; + + if(traj->type == Full) { + /* Adjusted these in the first pass */ + x = traj->x; + y = traj->y; + } else { + x = CUBIC(traj->xp, sp->time); + y = CUBIC(traj->yp, sp->time); + } + + ADD_ELEMENT(Trace, s, traj->object->trace->prev); + s->x = x; + s->y = y; + s->angle = traj->angle + traj->spin * (sp->time - traj->start); + s->divisions = traj->divisions; + traj->object->tracelen++; + traj->object->active = True; } + } else { /* expired */ + Trajectory *n = traj; + traj=traj->prev; + trajectory_destroy(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); + /* Erase end of trails */ + for (o = sp->objects->next; o != sp->objects; o = o->next) { + Trace *s; + for (s = o->trace->next; + o->trace->next != o->trace && + (o->count == 0 || o->tracelen > o->tail); + s = o->trace->next) { + ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s); + REMOVE(s); + o->tracelen--; + if(o->count <= 0 && o->tracelen <= 0) { + /* Object no longer in use and trail gone */ + Object *n = o; + o = o->prev; + object_destroy(n); + } + if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */ + } + } + + show_arms(mi, MI_BLACK_PIXEL(mi)); + cx = wander(sp, sp->time); + /* Reduce flicker by only permitting movements of more than a pixel */ + if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) { + show_figure(mi, MI_BLACK_PIXEL(mi), False); + sp->cx = cx; + } + + show_figure(mi, MI_WHITE_PIXEL(mi), False); + + show_arms(mi, MI_WHITE_PIXEL(mi)); + + /* Draw Objects */ + for (o = sp->objects->next; o != sp->objects; o = o->next) { + if(o->active) { + ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev); + o->active = False; } - /*** FIXME-END ***/ + } + + /* Save pattern name so we can erase it when it changes */ + if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) { + /* Erase old name */ + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi)); + XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + 0, 20, sp->pattern, strlen(sp->pattern)); + free(sp->pattern); + sp->pattern = strdup(pattern); + + if (MI_IS_VERBOSE(mi)) { + (void) fprintf(stderr, "Juggle[%d]: Running: %s\n", + MI_SCREEN(mi), sp->pattern); + } + } + if(mode_font != None && + XTextWidth(mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) { + /* Redraw once a cycle, in case it's obscured or it changed */ + XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi)); + XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + 0, 20, sp->pattern, strlen(sp->pattern)); + } + +#ifdef MEMTEST + if((int)(sp->time/10) % 1000 == 0) + (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0)); +#endif + + if (future < sp->time + 100 * THROW_CATCH_INTERVAL) { + refill_juggle(mi); + } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */ + release_juggle(mi); + init_juggle(mi); + } } #endif /* MODE_juggle */