From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- hacks/juggle.c | 2796 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2796 insertions(+) create mode 100644 hacks/juggle.c (limited to 'hacks/juggle.c') diff --git a/hacks/juggle.c b/hacks/juggle.c new file mode 100644 index 0000000..e6ff01f --- /dev/null +++ b/hacks/juggle.c @@ -0,0 +1,2796 @@ +/* -*- Mode: C; tab-width: 4 -*- */ +/* juggle */ + +#if 0 +static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore"; +#endif + +/*- + * Copyright (c) 1996 by Tim Auckland + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation. + * + * This file is provided AS IS with no warranties of any kind. The author + * shall have no liability with respect to the infringement of copyrights, + * trade secrets or any patents by this file or any part thereof. In no + * event will the author be liable for any lost revenue or profits or + * other special, indirect and consequential damages. + * + * Revision History + * 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 + * 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'.] + */ + +/* This code uses so many linked lists it's worth having a built-in + * leak-checker */ +#undef MEMTEST + +#ifdef STANDALONE +# define MODE_juggle +# define DEFAULTS "*delay: 10000 \n" \ + "*count: 200 \n" \ + "*cycles: 1000 \n" \ + "*ncolors: 32 \n" \ + "*font: -*-helvetica-bold-r-normal-*-180-*\n" \ + "*fpsSolid: true\n" \ + +# define release_juggle 0 +# define reshape_juggle 0 +# define juggle_handle_event 0 +# undef SMOOTH_COLORS +# include "xlockmore.h" /* in xscreensaver distribution */ +#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 "random" /* All patterns */ +#define DEF_TAIL "1" /* No trace */ +#ifdef UNI +/* 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 + +static char *pattern; +static int tail; +#ifdef UNI +static Bool uni; +#endif +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[] = +{ + {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL }, + {"-tail", ".juggle.tail", XrmoptionSepArg, NULL }, +#ifdef UNI + {"-uni", ".juggle.uni", XrmoptionNoArg, "on" }, + {"+uni", ".juggle.uni", XrmoptionNoArg, "off" }, +#endif + {"-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", DEF_PATTERN, t_String }, + { &tail, "tail", "Tail", DEF_TAIL, t_Int }, +#ifdef UNI + { &uni, "uni", "Uni", DEF_UNI, t_Bool }, +#endif + { &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" }, + { "-tail num", "Trace Juggling Patterns" }, +#ifdef UNI + { "-/+uni", "Unicycle" }, +#endif + { "-/+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." }, +}; + +ENTRYPOINT ModeSpecOpt juggle_opts = + { XtNumber(opts), opts, XtNumber(vars), vars, desc }; + +#ifdef USE_MODULES +ModStruct juggle_description = { + "juggle", "init_juggle", "draw_juggle", (char *) NULL, + "draw_juggle", "change_juggle", "free_juggle", &juggle_opts, + 10000, 200, 1000, 1, 64, 1.0, "", + "Shows a Juggler, juggling", 0, NULL +}; + +#endif + +#ifdef USE_XVMSUTILS +# include +#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 50 +#define ARMWIDTH ((int) (8.0 * sqrt(sp->scale))) +#define POSE 10 +#define BALLRADIUS ARMWIDTH + +#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) + +/******************************************************************** + * 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, + }, +}; + +/************************** + * Trajectory definitions * + **************************/ + +typedef enum {HEIGHT, ADAM} Notation; +typedef enum {Empty, Full, Ball} Throwable; +typedef enum {LEFT, RIGHT} Hand; +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; + + /* Throw */ + char posn; + int height; + int adam; + char *pattern; + char *name; + + /* Action */ + Hand hand; + Action action; + + /* LinkedAction */ + int color; + Object *object; + int divisions; + double angle, spin; + TrajectoryPtr balllink; + TrajectoryPtr handlink; + + /* PThratch */ + double cx; /* Moving juggler */ + double x, y; /* current position */ + double dx, dy; /* initial velocity */ + + /* Predictor */ + Throwable type; + unsigned long start, finish; + Spline xp, yp; + +#ifdef MEMTEST + char pad[1024]; +#endif +} Trajectory; + + +/******************* + * Pattern Library * + *******************/ + +typedef struct { + 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. */ +/* Can't const this because it is qsorted. This *should* be reentrant, + I think... */ +static /*const*/ patternstruct portfolio[] = { + {"[+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"}, + +}; + + + +typedef struct { int start; int number; } PatternIndex; + +struct patternindex { + int minballs; + int maxballs; + PatternIndex index[XtNumber(portfolio)]; +}; + + +/* Jugglestruct: per-screen global data. The master Wander, Object + * and Trajectory lists are anchored here. */ +typedef struct { + 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; + struct patternindex patternindex; + XFontStruct *mode_font; +} jugglestruct; + +static jugglestruct *juggles = (jugglestruct *) NULL; + +/******************* + * 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; \ + free(t); \ +} + +/* 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); \ + } + +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 +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 */ +} + +ENTRYPOINT void +free_juggle(ModeInfo * mi) { + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + + 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(sp->wander); + sp->wander = (Wander*)NULL; + } + if(sp->pattern != NULL) { + free(sp->pattern); + sp->pattern = NULL; + } + if (sp->mode_font!=None) { + XFreeFontInfo(NULL,sp->mode_font,1); + sp->mode_font = None; + } +} + +static Bool +add_throw(ModeInfo *mi, char type, int h, Notation n, const char* name) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + Trajectory *t; + + ADD_ELEMENT(Trajectory, t, sp->head->prev); + if(t == NULL){ /* Out of Memory */ + free_juggle(mi); + 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; + t->status = THRATCH; + } + return True; +} + +/* add a Thratch to the performance */ +static Bool +program(ModeInfo *mi, const char *patn, const char *name, int cycles) +{ + const char *p; + int w, h, i, seen; + Notation notation; + char type; + + if (MI_IS_VERBOSE(mi)) { + (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n", + MI_SCREEN(mi), (name == NULL) ? patn : name, cycles); + } + + 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') { + seen = 1; + h = 10*h + (*p - '0'); + } else { + Notation nn = notation; + switch (*p) { + case '[': /* begin Adam notation */ + 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 */ + seen = 1; + h = -1; + /* fall through */ + case ']': /* end Adam notation */ + nn = HEIGHT; + /* fall through */ + case ' ': + if (seen) { + i++; + if (!add_throw(mi, type, h, notation, title)) + return False; + title = NULL; + type=' '; + h = 0; + seen = 0; + } + notation = nn; + break; + default: + 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) { /* end of sequence */ + if (!add_throw(mi, type, h, notation, title)) + return False; + title = NULL; + } + } + return True; +} + +/* + ~~~~\~~~~~\~~~ + \\~\\~\~\\\~~~ + \\~\\\\~\\\~\~ + \\\\\\\\\\\~\\ + +[ 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) +{ + Trajectory *t, *p; + for(t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == ATCH) { + int a = t->adam; + t->height = 0; + for(p = t->next; a > 0; p = 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--; + } + t->height++; + } + 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; + } + } +} + +/* 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); + } + } +} + +/* Split Thratch notation into explicit throws and catches. + Usually Catch follows Throw in same hand, but take care of special + cases. */ + +/* ..n1.. -> .. LTn RT1 LC RC .. */ +/* ..nm.. -> .. LTn LC RTm RC .. */ + +static Bool +part(ModeInfo *mi) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + Trajectory *t, *nt, *p; + Hand hand = (LRAND() & 1) ? RIGHT : LEFT; + + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status > THRATCH) { + hand = t->hand; + } else if (t->status == THRATCH) { + char posn = '='; + + /* plausibility check */ + if (t->height <= 2 && t->posn == '_') { + t->posn = ' '; /* no short bounces */ + } + if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) { + t->posn = ' '; /* 1's need close catches */ + } + + switch (t->posn) { + /* throw catch */ + case ' ': 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; + 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 != sp->head) { + p = p->prev; /* '1's are thrown earlier than usual */ + } + + + + t->action = CATCH; + ADD_ELEMENT(Trajectory, nt, p); + if(nt == NULL){ + free_juggle(mi); + 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. */ + +static void +lob(ModeInfo *mi) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + Trajectory *t, *p; + int h; + for (t = sp->head->next; t != sp->head; t = t->next) { + if (t->status == ACTION) { + if (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->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; + } + } + + /* 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; + } + } + } + + if (t->height > 0) { + h = t->height - 1; + + /* search forward for next ball catch */ + 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 */ + t->balllink = p; /* complete trajectory */ +# if 0 + if (p->type == Full) { + (void) fprintf(stderr, "juggle[%d]: Dropped %d\n", + MI_SCREEN(mi), t->object->color); + } +#endif + p->type = Full; + DUP_OBJECT(p, t); /* accept catch */ + p->angle = t->angle; + p->divisions = t->divisions; + } + } + } + } + t->type = Empty; /* thrown */ + } else if (t->action == CATCH) { + /* search forward for next throw from this hand */ + for (p = t->next; t->handlink == NULL; p = p->next) { + if(p->status < ACTION || p == sp->head) return; + if (p->action == THROW && p->hand == t->hand) { + p->type = t->type; /* pass ball */ + DUP_OBJECT(p, t); /* pass object */ + p->divisions = t->divisions; + t->handlink = p; + } + } + } + t->status = LINKEDACTION; + } + } +} + +/* Clap when both hands are empty */ +static void +clap(jugglestruct *sp) +{ + Trajectory *t, *p; + for (t = sp->head->next; t != sp->head; t = t->next) { + 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 */ + + } + break; /* Only need first catch */ + } + } + } + } +} + +#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d) + +/* Compute single spline from x0 with velocity dx0 at time t0 to x1 + with velocity dx1 at time t1 */ +static Spline +makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1) +{ + Spline s; + double a, b, c, d; + double x10; + double t10; + + x10 = x1 - x0; + t10 = t1 - t0; + a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10); + b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10); + c = dx0; + d = x0; + s.a = a; + s.b = -3*a*t0 + b; + s.c = (3*a*t0 - 2*b)*t0 + c; + s.d = ((-a*t0 + b)*t0 - c)*t0 +d; + return s; +} + +/* 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, + double x0, double dx0, int t0, + double x1, int t1, + double x2, double dx2, int t2) +{ + double x10, x21, t21, t10, t20, dx1; + x10 = x1 - x0; + x21 = x2 - x1; + t21 = t2 - t1; + t10 = t1 - t0; + t20 = t2 - t0; + dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21 + - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) / + (2*t10*t21*t20); + *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1); + *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2); + return dx1; +} + +/* 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) +{ + 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; +} + + + +/* 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; + } + } + 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); +} + +#define SX 25 /* Shoulder Width */ + +/* Convert hand position symbols into actual time/space coordinates */ +static void +positions(jugglestruct *sp) +{ + 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) { + 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 */ + + double xo = 0, yo; + double sx = SX; + double pose = SX/2; + + /* 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); + } + } + + if(t->start == 0) + t->start = now; + else /* Concatenated performances may need clock resync */ + now = t->start; + + t->cx = wander(sp, t->start); + + /* space */ + yo = 90; + + /* Add room for the handle */ + if(t->action == CATCH && t->object != NULL) + yo -= ObjectDefs[t->object->type].handle; + + 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; + } + + 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; + } + } + } +} + + +/* Private physics functions */ + +/* 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; + } +} + + +/* 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 */ +static void +dump(jugglestruct *sp) +{ + Trajectory *t; + for (t = sp->head->next; t != sp->head; t = t->next) { + switch (t->status) { + 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: + 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, "%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, "%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 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, "%p: status %d not implemented\n", + (void*)t, t->status); + break; + } + } + (void) fprintf(stderr, "---\n"); +} +#endif + +static int get_num_balls(const char *j) +{ + int balls = 0; + const char *p; + int h = 0; + if (!j) abort(); + for (p = j; *p; p++) { + if (*p >= '0' && *p <='9') { /* digit */ + h = 10*h + (*p - '0'); + } else { + if (h > balls) { + balls = h; + } + h = 0; + } + } + return balls; +} + +#ifdef __cplusplus +extern "C" { +#endif + +static int +compare_num_balls(const void *p1, const void *p2) +{ + int i, j; + i = get_num_balls(((patternstruct*)p1)->pattern); + j = get_num_balls(((patternstruct*)p2)->pattern); + if (i > j) { + return (1); + } else if (i < j) { + return (-1); + } else { + return (0); + } +} + +#ifdef __cplusplus +} +#endif + + +/************************************************************************** + * Rendering Functions * + * * + **************************************************************************/ + +static void +show_arms(ModeInfo * mi, unsigned long color) +{ + 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); + } +} + +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); + } + + 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); +} + +#if 0 +static void +show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s) +{ + 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(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); + } + + 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)); + } + + 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); + + 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; + } + + /* 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); + } + 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)); + } + + /* 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); +} + +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); +} + + +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); +} + +/************************************************************************** + * Public Functions * + * * + **************************************************************************/ + + +/* 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 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; + 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 */ + } + if (new_balls < sp->patternindex.minballs) { + new_balls += 2; + } + if (new_balls > sp->patternindex.maxballs) { + new_balls -= 2; + } + if (new_balls < sp->num_balls) { + if (!program(mi, "[*]", NULL, 1)) /* lose ball */ + return; + } + sp->num_balls = new_balls; + } + + count += t; + if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) { + /* Pick from PortFolio */ + int p = sp->patternindex.index[sp->num_balls].start + + NRAND(sp->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; + } + 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, NULL, MI_CYCLES(mi))) + return; + } + + adam(sp); + + name(sp); + + if (!part(mi)) + return; + + lob(mi); + + clap(sp); + + positions(sp); + + if (!projectile(sp)) { + free_juggle(mi); + return; + } + + hands(sp); +#ifdef DEBUG + if(MI_IS_DEBUG(mi)) dump(sp); +#endif +} + +static void +change_juggle(ModeInfo * mi) +{ + jugglestruct *sp = NULL; + Trajectory *t; + + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; + + /* 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); + } + } + + /* Pick the current object theme */ + sp->objtypes = choose_object(); + + 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); + +} + +ENTRYPOINT void +init_juggle (ModeInfo * mi) +{ + jugglestruct *sp = 0; + int i; + + MI_INIT (mi, juggles); + sp = &juggles[MI_SCREEN(mi)]; + + 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 (sp->head == 0) { /* first time initializing this juggler */ + + sp->count = ABS(MI_COUNT(mi)); + if (sp->count == 0) + sp->count = 200; + + /* record start time */ + sp->begintime = time(NULL); + if(sp->patternindex.maxballs > 0) { + sp->num_balls = sp->patternindex.minballs + + NRAND(sp->patternindex.maxballs - sp->patternindex.minballs); + } + + 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(mi); + return; + } + + /* create circular object list */ + ADD_ELEMENT(Object, sp->objects, sp->objects); + if(sp->objects == NULL){ + free_juggle(mi); + return; + } + + /* create circular wander list */ + ADD_ELEMENT(Wander, sp->wander, sp->wander); + if(sp->wander == NULL){ + free_juggle(mi); + return; + } + (void)wander(sp, 0); /* Initialize wander */ + + sp->pattern = strdup(""); /* Initialise saved pattern with + free-able memory */ + } + + sp = &juggles[MI_SCREEN(mi)]; + + if (pattern && + (!*pattern || + !strcasecmp (pattern, ".") || + !strcasecmp (pattern, "random"))) + pattern = NULL; + + if (pattern == NULL && sp->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 */ + sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern); + /* run through sorted list, indexing start of each group + and number in group */ + sp->patternindex.maxballs = 1; + for (i = 0; i < nelements; i++) { + int b = get_num_balls(portfolio[i].pattern); + if (b > sp->patternindex.maxballs) { + sp->patternindex.index[sp->patternindex.maxballs].number = numpat; + if(numpat == 0) sp->patternindex.minballs = b; + sp->patternindex.maxballs = b; + numpat = 1; + sp->patternindex.index[sp->patternindex.maxballs].start = i; + } else { + numpat++; + } + } + sp->patternindex.index[sp->patternindex.maxballs].number = numpat; + } + + /* Set up programme */ + change_juggle(mi); + + /* 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)); + + /* Only put things here that won't interrupt the programme during + a window resize */ + + /* 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); + + if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */ + char *font = get_string_resource (MI_DISPLAY(mi), "font", "Font"); + sp->mode_font = load_font_retry(MI_DISPLAY(mi), font); + } +} + +ENTRYPOINT 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; + +#ifdef HAVE_JWXYZ + /* Don't worry about flicker, trust Quartz's double-buffering. + This is a fast fix for the pixel-turds I can't track down... + */ + XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi)); +#endif + + /* 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; + } + 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 (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 { + /* Balls are always caught at the bottom */ + xd = 0; + yd = -4; + } + } + 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); + } + } + + /* 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; + } + } + + + /* 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)); +# if 0 + XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + 0, 20, sp->pattern, strlen(sp->pattern)); +# else + XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi), + 0, 0, MI_WIDTH(mi), 25); +# endif + 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(sp->mode_font != None && + XTextWidth(sp->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)); + XDrawImageString(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 */ + init_juggle(mi); + } +} + +XSCREENSAVER_MODULE ("Juggle", juggle) + +#endif /* MODE_juggle */ -- cgit v1.2.3-55-g7522