X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fjuggler3d.c;h=5697ed263af32c44e98aed77dde5e2e6320627c5;hp=cdf6187a502c4db9c68de40b4cf9457d19a136e8;hb=f0261d8acab611f3433160e4f07367b870439739;hpb=7b34ef992563d7bcbb64cc5597dc45fa24470b05 diff --git a/hacks/glx/juggler3d.c b/hacks/glx/juggler3d.c index cdf6187a..5697ed26 100644 --- a/hacks/glx/juggler3d.c +++ b/hacks/glx/juggler3d.c @@ -1,2146 +1,3067 @@ -/* Juggler3D, Copyright (c) 2005-2008 Brian Apps +/* juggle, Copyright (c) 1996-2009 Tim Auckland + * and Jamie Zawinski * - * Permission to use, copy, modify, distribute, and sell this software and its - * documentation for any purpose is hereby granted without fee, provided that - * the above copyright notice appear in all copies and that both that copyright - * notice and this permission notice appear in supporting documentation. No - * representations are made about the suitability of this software for any - * purpose. It is provided "as is" without express or implied warranty. */ - -#undef countof -#define countof(x) (sizeof((x))/sizeof((*x))) - -#define DEFAULTS \ - "*delay: 20000\n*showFPS: False\n*wireframe: False\n" - -# define refresh_juggler3d 0 -# define release_juggler3d 0 -#include "xlockmore.h" -#include "gltrackball.h" - -#ifdef USE_GL /* whole file */ - -/* A selection of macros to make functions from math.h return single precision - * numbers. Arguably it's better to work at a higher precision and cast it - * back but littering the code with casts makes it less readable -- without - * the casts you can get tons of warnings from the compiler (particularily - * MSVC which enables loss of precision warnings by default) */ - -#define cosf(a) (float)(cos((a))) -#define sinf(a) (float)(sin((a))) -#define tanf(a) (float)(tan((a))) -#define sqrtf(a) (float)(sqrt((a))) -#define powf(a, b) (float)(pow((a), (b))) - -#undef max -#undef min + * 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. + * + * NOTE: this program was originally called "juggle" and was 2D Xlib. + * There was another program called "juggler3d" that was OpenGL. + * In 2009, jwz converted "juggle" to OpenGL and renamed + * "juggle" to "juggler3d". The old "juggler3d" hack is gone. + * + * Revision History + * 09-Aug-2009: jwz: converted from Xlib to OpenGL. + * 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 + */ -#define max(a, b) ((a) > (b) ? (a) : (b)) -#define min(a, b) ((a) < (b) ? (a) : (b)) +/*- + * 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 * - * The code is broadly split into the following parts: + * 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. * - * - Engine. The process of determining the position of the juggler and - * objects being juggled at an arbitrary point in time. This is - * independent from any drawing code. - * - Sites. The process of creating a random site swap pattern or parsing - * a Juggle Saver compatible siteswap for use by the engine. For an - * introduction to juggling site swaps check out - * http://www.jugglingdb.com/ - * - Rendering. OpenGL drawing code that animates the juggler. - * - XScreenSaver. Interface code to get thing working as a GLX hack. - * - *****************************************************************************/ - - -/***************************************************************************** + * To go from a-notation to s-notation, you start by mapping each a_n + * to a permutation of N, the natural numbers. * - * Data structures + * 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. * - *****************************************************************************/ - -/* POS is used to represent the position of a hand when it catches or throws - * an object; as well as the orientation of the object. The rotation and - * elevation are specified in degrees. These angles are not normalised so that - * it is possible to specify how the object spins and rotates as it is thrown - * from the 'From' position to the 'To' position. - * - * Because this is the position of the hand some translation is required with - * rings and clubs to get the centre of rotation position. */ - -typedef struct -{ - float x; - float y; - float z; - float Rot; - float Elev; -} POS; - - -/* An array of THROW_INFOs are configured with each entry corresponding to the - * position in the site swap (In fact we double up odd length patterns to ensure - * there is left/right symmetry). It allows us to quickly determine where an - * object and the hands are at a given time. The information is specified in - * terms of throws, and positions where throws aren't present (0's and 2's) are - * simply ignored. - * - * TotalTime - The count of beats before this object is thrown again. Typically - * this is the same as the weight of the throw but where an object is held it - * is longer. e.g. the first throw of the site 64242.7. will be 10, 6 for - * throw and 4 (two 2's) for the carry. - * TimeInAir - The weight of the throw. - * PrevThrow - zero based index into array of THROW_INFOs of the previous throw. - * e.g. for the throw '8' in the site 345678..... the PrevThrow is 1 - * (i.e. the 4) - * FromPos, FromVelocity, ToPos, ToVelocity - The position and speeds at the - * start and end of the throw. These are used to generate a spline while - * carrying an object and while moving the hand from a throw to a catch. - * NextForHand - Number of beats before the hand that throws this object will - * throw another object. This is always going to be at least 2. When there - * are gaps in the pattern (0's) or holds (2's) NextForHand increases. */ - -typedef struct -{ - int TotalTime; - int TimeInAir; - int PrevThrow; - - POS FromPos; - POS FromVelocity; - POS ToPos; - POS ToVelocity; - - int NextForHand; -} THROW_INFO; - - -/* OBJECT_POSITION works with the array of THROW_INFOs to allow us to determine - * exactly where an object or hand is. + * 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. * - * TimeOffset - The total number of beats expired when the object was thrown. - * ThrowIndex - The zero based index into the THROW_INFO array for the current - * throw. - * ObjectType - One of the OBJECT_XX defines. - * TotalTwist - Only relevant for OBJECT_BALL, this is the total amount the ball - * has twisted while in the air. When segmented balls are drawn you see a - * spinning effect similar to what happens when you juggle beanbags. */ - -#define OBJECT_DEFAULT 0 -#define OBJECT_BALL 1 -#define OBJECT_CLUB 2 -#define OBJECT_RING 3 - -typedef struct -{ - int TimeOffset; - int ThrowIndex; - float TotalTwist; - int ObjectType; -} OBJECT_POSITION; - - -/* PATTERN_INFO is the main structure that holds the information about a - * juggling pattern. + * 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: * - * pThrowInfo is an array of ThrowLen elements that describes each throw in the - * pattern. - * pObjectInfo gives the current position of all objects at a given instant. - * These values are updated as the pattern is animated. - * LeftHand and RightHand describe the current positions of each of the - * juggler's hands. - * MaxWeight is the maximum weight of the all throws in pThrowInfo. - * Height and Alpha are parameters that describe how objects fall under the - * influence of gravity. See SetHeightAndAlpha() for the gory details. */ - -typedef struct -{ - THROW_INFO* pThrowInfo; - int ThrowLen; - - OBJECT_POSITION* pObjectInfo; - int Objects; - - OBJECT_POSITION LeftHand; - OBJECT_POSITION RightHand; - - int MaxWeight; - - float Height; - float Alpha; -} PATTERN_INFO; - - -/* EXT_SITE_INFO is used to initialise a PATTERN_INFO object using a Juggle - * Saver compatible site swap. These contain additional information about the - * type of object thrown, the positions of throw and catch etc. */ - -#define HAS_FROM_POS 1 -#define HAS_TO_POS 2 -#define HAS_SNATCH 4 -#define HAS_SPINS 8 - -typedef struct -{ - unsigned Flags; - int Weight; - int ObjectType; - POS FromPos; - POS ToPos; - float SnatchX; - float SnatchY; - int Spins; -} EXT_SITE_INFO; - - -/* RENDER_STATE is used to co-ordinate the OpenGL rendering of the juggler and - * objects: - * pPattern - The pattern to be juggled - * CameraElev - The elevation angle (in degrees) that the camera is looking - * along. 0 is horizontal and a +ve angle is looking down. This value - * should be between -90 and +90. - * AspectRatio - Window width to height ratio. - * DLStart - The number for the first display list created, any others directly - * follow this. - * Time - Animation time (in beats) - * TranslateAngle - Cumulative translation (in degrees) for the juggling figure. - * SpinAngle- Cumulative spin (in degrees) for the juggling figure. - */ - -typedef struct -{ - PATTERN_INFO* pPattern; - float CameraElev; - float AspectRatio; - int DLStart; - - float Time; - float TranslateAngle; - float SpinAngle; - - trackball_state *trackball; - Bool button_down_p; - -} RENDER_STATE; - - -/***************************************************************************** + * (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 * - * Engine + * ...55555566771771771771771... * - **************************************************************************** + * So the answer is to do two 6 throws and then go straight into + * (771). Coming back down of course, * - * The main purpose of the engine is to work out the exact position of all the - * juggling objects and the juggler's hands at any point in time. The motion - * of the objects can be split into two parts: in the air and and being carried. + * ...5515515515515515555555555... * - * While in the air, the motion is governed by a standard parabolic trajectory. - * The only minor complication is that the engine has no fixed concept of - * gravity, instead it using a term called Alpha that varies according to the - * pattern (see SetHeightAndAlpha). + * converts to * - * The motion while an object is carried comes from fitting a spline through the - * catch and throw points and maintaining the catch and throw velocities at - * each end. In the simplest case this boils down to cubic Bezier spline. The - * only wrinkle occurs when a ball is being carried for a long time. The simple - * cubic spline maths produces a curve that goes miles away -- here we do a - * bit of reparameterisation so things stay within sensible bounds. - * (On a related note, this scheme is _much_ simpler than the Juggle Saver - * one. Juggle Saver achieves 2nd order continuity and much care is taken - * to avoid spline ringing.) - * - * The motion of the hands is identical to the ball carrying code. It uses two - * splines: one while an object is being carried; and another when it moves from - * the previous throw to the next catch. + * ...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'.] */ - -static const float CARRY_TIME = 0.56f; -static const float PI = 3.14159265358979f; +/* This code uses so many linked lists it's worth having a built-in + * leak-checker */ +#undef MEMTEST -/* While a ball is thrown it twists slighty about an axis, this routine gives - * the total about of twist for a given ball throw. */ - -static float GetBallTwistAmount(const THROW_INFO* pThrow) -{ - if (pThrow->FromPos.x > pThrow->ToPos.x) - return 18.0f * powf(pThrow->TimeInAir, 1.5); - else - return -18.0f * powf(pThrow->TimeInAir, 1.5); -} +# define DEFAULTS "*delay: 10000 \n" \ + "*count: 200 \n" \ + "*cycles: 1000 \n" \ + "*ncolors: 32 \n" \ + "*titleFont: -*-helvetica-bold-r-normal-*-180-*\n" \ + "*showFPS: False \n" \ + "*wireframe: False \n" \ +# define refresh_juggle 0 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) -static float NormaliseAngle(float Ang) -{ - if (Ang >= 0.0f) - { - int i = (int) (Ang + 180.0f) / 360; - return Ang - 360.0f * i; - } - else - { - int i = (int)(180.0f - Ang) / 360; - return Ang + i * 360.0f; - } -} - +#include "xlockmore.h" +#include "sphere.h" +#include "tube.h" +#include "rotator.h" +#include "gltrackball.h" +#include "glxfonts.h" +#include -/* The interpolate routine for ball carrying and hand motion. We are given the - * start (P0) and end (P1) points and the velocities at these points, the task - * is to form a function P(t) such that: - * P(0) = P0 - * P(TLen) = P1 - * P'(0) = V0 - * P'(TLen) = V1 - */ +#ifdef USE_GL /* whole file */ -static POS InterpolatePosition( - const POS* pP0, const POS* pV0, const POS* pP1, const POS* pV1, - float TLen, float t) -{ - POS p; - float a, b, c, d, tt, tc; - - /* The interpolation is based on a simple cubic that achieves 1st order - * continuity at the end points. However the spline can become too long if - * the TLen parameter is large. In this case we cap the curve's length (fix - * the shape) and then reparameterise time to achieve the continuity - * conditions. */ - - tc = CARRY_TIME; - - if (TLen > tc) - { - /* The reparameterisation tt(t) gives: - * tt(0) = 0, tt(TLen) = tc, tt'(0) = 1, tt'(TLen) = 1 - * and means we can set t = tt(t), TLen = tc and then fall through - * to use the normal cubic spline fit. - * - * The reparameterisation is based on two piecewise quadratics, one - * that goes from t = 0 to t = TLen / 2 and the other, mirrored in - * tt and t that goes from t = TLen / 2 to t = TLen. - * Because TLen > tc we can arrange for tt to be unique in the range if - * we specify the quadratic in tt. i.e. t = A * tt ^ 2 + B * tt + C. - * - * Considering the first piece and applying initial conditions. - * tt = 0 when t = 0 => C = 0 - * tt' = 1 when t = 0 => B = 1 - * tt = tc / 2 when t = TLen / 2 => A = 2 * (TLen - tc) / tc^2 - * - * writing in terms of t - * tt = (-B + (B ^ 2 + 4At) ^ 0.5) / 2A - * or - * tt = ((1 + 4At) ^ 0.5 - 1) / 2A */ - - float A = 2.0f * (TLen - tc) / (tc * tc); - - if (t > TLen / 2.0f) - t = tc - (sqrtf(1.0f + 4.0f * A * (TLen - t)) - 1.0f) / (2.0f * A); - else - t = (sqrtf(1.0f + 4.0f * A * t) - 1.0f) / (2.0f * A); - - TLen = tc; - } - - /* The cubic spline takes the form: - * P(t) = p0 * a(t) + v0 * b(t) + p1 * c(t) + v1 * d(t) - * where p0 is the start point, v0 the start velocity, p1 the end point and - * v1 the end velocity. a(t), b(t), c(t) and d(t) are cubics in t. - * We can show that: - * - * a(t) = 2 * (t / TLen) ^ 3 - 3 * (t / TLen) ^ 2 + 1 - * b(t) = t ^ 3 / TLen ^ 2 - 2 * t ^ 2 / TLen + t - * c(t) = -2 * (t / TLen) ^ 3 + 3 * (t / TLen) ^ 2 - * d(t) = t ^ 3 / TLen ^ 2 - t ^ 2 / TLen - * - * statisfy the boundary conditions: - * P(0) = p0, P(TLen) = p1, P'(0) = v0 and P'(TLen) = v1 */ - - tt = t / TLen; - - a = tt * tt * (2.0f * tt - 3.0f) + 1.0f; - b = t * tt * (tt - 2.0f) + t; - c = tt * tt * (3.0f - 2.0f * tt); - d = t * tt * (tt - 1.0f); - - p.x = a * pP0->x + b * pV0->x + c * pP1->x + d * pV1->x; - p.y = a * pP0->y + b * pV0->y + c * pP1->y + d * pV1->y; - p.z = a * pP0->z + b * pV0->z + c * pP1->z + d * pV1->z; - - p.Rot = a * NormaliseAngle(pP0->Rot) + b * pV0->Rot + - c * NormaliseAngle(pP1->Rot) + d * pV1->Rot; - p.Elev = a * NormaliseAngle(pP0->Elev) + b * pV0->Elev + - c * NormaliseAngle(pP1->Elev) + d * pV1->Elev; - - return p; -} +#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 */ + +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 POS InterpolateCarry( - const THROW_INFO* pThrow, const THROW_INFO* pNext, float t) +static OptionStruct desc[] = { - float CT = CARRY_TIME + pThrow->TotalTime - pThrow->TimeInAir; - return InterpolatePosition(&pThrow->ToPos, &pThrow->ToVelocity, - &pNext->FromPos, &pNext->FromVelocity, CT, t); -} + { "-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 = + {countof(opts), opts, countof(vars), vars, desc}; + + +/* 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 + +/* build all the models assuming a 480px high scene */ +#define SCENE_HEIGHT 480 +#define SCENE_WIDTH ((int)(SCENE_HEIGHT*(MI_WIDTH(mi)/(float)MI_HEIGHT(mi)))) + +/*#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 int (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 GLfloat body_color_1[4] = { 0.9, 0.7, 0.5, 1 }; +static const GLfloat body_color_2[4] = { 0.6, 0.4, 0.2, 1 }; + +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, + }, +}; -/* Determine the position of the hand at a point in time. */ +/************************** + * 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]; + + +/* 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"}, -static void GetHandPosition( - PATTERN_INFO* pPattern, int RightHand, float Time, POS* pPos) -{ - OBJECT_POSITION* pObj = - RightHand == 0 ? &pPattern->LeftHand : &pPattern->RightHand; - THROW_INFO* pLastThrow; - - /* Upon entry, the throw information for the relevant hand may be out of - * sync. Therefore we advance through the pattern if required. */ - - while (pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand + pObj->TimeOffset - <= (int) Time) - { - int w = pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand; - pObj->TimeOffset += w; - pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen; - } +}; - pLastThrow = &pPattern->pThrowInfo[pObj->ThrowIndex]; - /* The TimeInAir will only ever be 2 or 0 if no object is ever thrown by - * this hand. In normal circumstances, 2's in the site swap are coalesced - * and added to TotalTime of the previous throw. 0 is a hole and means that - * an object isn't there. In this case we just hold the hand still. */ - if (pLastThrow->TimeInAir == 2 || pLastThrow->TimeInAir == 0) - { - pPos->x = pLastThrow->FromPos.x; - pPos->y = pLastThrow->FromPos.y; - } - else - { - /* The hand is either moving to catch the next object or carrying the - * next object to its next throw position. The way THROW_INFO is - * structured means the relevant information for the object we're going - * to catch is held at the point at which it was thrown - * (pNextThrownFrom). We can't go straight for it and instead have to - * look at the object we've about to throw next and work out where it - * came from. */ - - THROW_INFO* pNextThrow = &pPattern->pThrowInfo[ - (pObj->ThrowIndex + pLastThrow->NextForHand) % pPattern->ThrowLen]; - - THROW_INFO* pNextThrownFrom = - &pPattern->pThrowInfo[pNextThrow->PrevThrow]; - - /* tc is a measure of how long the object we're due to catch is being - * carried for. We use this to work out if we've actually caught it at - * this moment in time. */ - - float tc = CARRY_TIME + - pNextThrownFrom->TotalTime - pNextThrownFrom->TimeInAir; - - Time -= pObj->TimeOffset; - if (Time > pLastThrow->NextForHand - tc) - { - /* carrying this ball to it's new location */ - *pPos = InterpolateCarry(pNextThrownFrom, - pNextThrow, (Time - (pLastThrow->NextForHand - tc))); - } - else - { - /* going for next catch */ - *pPos = InterpolatePosition( - &pLastThrow->FromPos, &pLastThrow->FromVelocity, - &pNextThrownFrom->ToPos, &pNextThrownFrom->ToVelocity, - pLastThrow->NextForHand - tc, Time); - } - } -} +typedef struct { int start; int number; } PatternIndex; +struct patternindex { + int minballs; + int maxballs; + PatternIndex index[countof(portfolio)]; +}; -static float SinDeg(float AngInDegrees) -{ - return sinf(AngInDegrees * PI / 180.0f); + +/* Jugglestruct: per-screen global data. The master Object + * and Trajectory lists are anchored here. */ +typedef struct { + GLXContext *glx_context; + rotator *rot; + trackball_state *trackball; + Bool button_down_p; + + double scale; + 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; + GLuint font_dlist; +} 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); \ +} -static float CosDeg(float AngInDegrees) +/* 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) { - return cosf(AngInDegrees * PI / 180.0f); + 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 */ +} -/* Offset the specified position to get the centre of the object based on the - * the handle length and the current orientation */ +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->pattern != NULL) { + free(sp->pattern); + sp->pattern = NULL; + } + if (sp->mode_font!=None) { + XFreeFontInfo(NULL,sp->mode_font,1); + sp->mode_font = None; + } +} -static void OffsetHandlePosition(const POS* pPos, float HandleLen, POS* pResult) +static Bool +add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name) { - pResult->x = pPos->x + HandleLen * SinDeg(pPos->Rot) * CosDeg(pPos->Elev); - pResult->y = pPos->y + HandleLen * SinDeg(pPos->Elev); - pResult->z = pPos->z + HandleLen * CosDeg(pPos->Rot) * CosDeg(pPos->Elev); - pResult->Elev = pPos->Elev; - pResult->Rot = pPos->Rot; + Trajectory *t; + + 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; + t->status = THRATCH; + } + return True; } - -static void GetObjectPosition( - PATTERN_INFO* pPattern, int Obj, float Time, float HandleLen, POS* pPos) +/* add a Thratch to the performance */ +static Bool +program(ModeInfo *mi, const char *patn, const char *name, int cycles) { - OBJECT_POSITION* pObj = &pPattern->pObjectInfo[Obj]; - THROW_INFO* pThrow; - - /* Move through the pattern, if required, such that pThrow corresponds to - * the current throw for this object. */ - - while (pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime + pObj->TimeOffset - <= (int) Time) - { - int w = pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime; - pObj->TimeOffset += w; - pObj->TotalTwist = NormaliseAngle(pObj->TotalTwist + - GetBallTwistAmount(&pPattern->pThrowInfo[pObj->ThrowIndex])); - - pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen; - } - - pThrow = &pPattern->pThrowInfo[pObj->ThrowIndex]; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + 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(sp, 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(sp, type, h, notation, title)) + return False; + title = NULL; + } + } + return True; +} - if (pThrow->TimeInAir == 2 || pThrow->TimeInAir == 0) - { - *pPos = pThrow->FromPos; - OffsetHandlePosition(pPos, HandleLen, pPos); - } - else - { - float tc = pThrow->TimeInAir - CARRY_TIME; - float BallTwist = GetBallTwistAmount(pThrow); - Time -= pObj->TimeOffset; - if (Time < tc) - { - /* object in air */ - POS From, To; - float t, b; - - t = Time / tc; - - OffsetHandlePosition(&pThrow->FromPos, HandleLen, &From); - OffsetHandlePosition(&pThrow->ToPos, HandleLen, &To); - - b = (To.y - From.y) / tc + pPattern->Alpha * tc; - - pPos->x = (1.0f - t) * From.x + t * To.x; - pPos->z = (1.0f - t) * From.z + t * To.z; - pPos->y = -pPattern->Alpha * Time * Time + b * Time + From.y; - - if (pObj->ObjectType == OBJECT_BALL) - pPos->Rot = pObj->TotalTwist + t * BallTwist; - else - { - /* We describe the rotation of a club (or ring) with an - * elevation and rotation but don't include a twist. - * If we ignore twist for the moment, the orientation at a - * rotation of r and an elevation of e can be also be expressed - * by rotating the object a further 180 degrees and sort of - * mirroring the rotation, e.g.: - * rot = r + 180 and elev = 180 - e - * We can easily show that the maths holds, consider the - * x, y ,z position of the end of a unit length club. - * y = sin(180 - e) = sin(e) - * x = cos(180 - e) * sin(r + 180) = -cos(e) * - sin(r) - * z = cos(180 - e) * cos(r + 180) = -cos(e) * - cos(r) - * When a club is thrown these two potential interpretations - * can produce unexpected results. - * The approach we adopt is that we try and minimise the amount - * of rotation we give a club -- normally this is what happens - * when juggling since it's much easier to spin the club. - * - * When we come to drawing the object the two interpretations - * aren't identical, one causes the object to twist a further - * 180 about its axis. We avoid the issue by ensuring our - * objects have rotational symmetry of order 2 (e.g. we make - * sure clubs have an even number of stripes) this makes the two - * interpretations appear identical. */ - - float RotAmt = NormaliseAngle(To.Rot - From.Rot); - - if (RotAmt < -90.0f) - { - To.Elev += 180 - 2 * NormaliseAngle(To.Elev); - RotAmt += 180.0f; - } - else if (RotAmt > 90.0f) - { - To.Elev += 180 - 2 * NormaliseAngle(To.Elev); - RotAmt -= 180.0f; - } - - pPos->Rot = From.Rot + t * RotAmt; - } +/* + ~~~~\~~~~~\~~~ + \\~\\~\~\\\~~~ + \\~\\\\~\\\~\~ + \\\\\\\\\\\~\\ - pPos->Elev = (1.0f - t) * From.Elev + t * To.Elev; +[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ] - } - else - { - THROW_INFO* pNextThrow = &pPattern->pThrowInfo[ - (pObj->ThrowIndex + pThrow->TotalTime) % pPattern->ThrowLen]; +4 4 1 3 12 2 4 1 4 4 13 0 3 1 - *pPos = InterpolateCarry(pThrow, pNextThrow, Time - tc); +*/ +#define BOUNCEOVER 10 +#define KICKMIN 7 +#define THROWMAX 20 - if (pObj->ObjectType == OBJECT_BALL) - pPos->Rot = pObj->TotalTwist + BallTwist; +/* 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; + } + } +} - OffsetHandlePosition(pPos, HandleLen, pPos); - } - } +/* 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. */ -/* Alpha is used to represent the acceleration due to gravity (in fact - * 2 * Alpha is the acceleration). Alpha is adjusted according to the pattern - * being juggled. My preference is to slow down patterns with lots of objects - * -- they move too fast in realtime. Also I prefer to see a balance between - * the size of the figure and the height of objects thrown -- juggling patterns - * with large numbers of objects under real gravity can mean balls are lobbed - * severe heights. Adjusting Alpha achieves both these goals. - * - * Basically we pick a height we'd like to see the biggest throw reach and then - * adjust Alpha to meet this. */ +/* ..n1.. -> .. LTn RT1 LC RC .. */ +/* ..nm.. -> .. LTn LC RTm RC .. */ -static void SetHeightAndAlpha(PATTERN_INFO* pPattern, - const int* Site, const EXT_SITE_INFO* pExtInfo, int Len) +static Bool +part(jugglestruct *sp) { - float H; - int MaxW = 5; - int i; - - if (Site != NULL) - { - for (i = 0; i < Len; i++) - MaxW = max(MaxW, Site[i]); - } - else - { - for (i = 0; i < Len; i++) - MaxW = max(MaxW, pExtInfo[i].Weight); - } - - /* H is the ideal max height we'd like our objects to reach. The formula - * was developed by trial and error and was simply stolen from Juggle Saver. - * Alpha is then calculated from the classic displacement formula: - * s = 0.5at^2 + ut (where a = 2 * Alpha) - * We know u (the velocity) is zero at the peak, and the object should fall - * H units in half the time of biggest throw weight. - * Finally we determine the proper height the max throw reaches since this - * may not be H because capping may be applied (e.g. for max weights less - * than 5). */ - - H = 8.0f * powf(MaxW / 2.0f, 0.8f) + 5.0f; - pPattern->Alpha = (2.0f * H) / powf(max(5, MaxW) - CARRY_TIME, 2.0f); - pPattern->Height = pPattern->Alpha * powf((MaxW - CARRY_TIME) * 0.5f, 2); + 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(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; +} -/* Where positions and spin info is not specified, generate suitable default - * values. */ +/* Connnect up throws and catches to figure out which ball goes where. + Do the same with the juggler's hands. */ -static int GetDefaultSpins(int Weight) +static void +lob(ModeInfo *mi) { - if (Weight < 3) - return 0; - else if (Weight < 4) - return 1; - else if (Weight < 7) - return 2; - else - return 3; + 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 */ + /* no, that looks stupid. -jwz */ + if (t->divisions < 1) + 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; + } + } } - -static void GetDefaultFromPosition(unsigned char Side, int Weight, POS* pPos) +/* Clap when both hands are empty */ +static void +clap(jugglestruct *sp) { - if (Weight > 4 && Weight % 2 != 0) - pPos->x = Side ? -0.06f : 0.06f; - else if (Weight == 0 || Weight == 2) - pPos->x = Side ? 1.6f : -1.6f; - else - pPos->x = Side? 0.24f : -0.24f; - - pPos->y = (Weight == 2 || Weight == 0) ? -0.25f : 0.0f; - - pPos->Rot = (Weight % 2 == 0 ? -23.5f : 27.0f) * (Side ? -1.0f : 1.0f); - - pPos->Elev = Weight == 1 ? -30.0f : 0.0f; - pPos->z = 0.0f; + 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) -static void GetDefaultToPosition(unsigned char Side, int Weight, POS* pPos) +/* 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) { - if (Weight == 1) - pPos->x = Side ? -1.0f : 1.0f; - else if (Weight % 2 == 0) - pPos->x = Side ? 2.8f : -2.8f; - else - pPos->x = Side? -3.1f : 3.1f; - - pPos->y = -0.5f; - - pPos->Rot = (Side ? -35.0f : 35.0f) * (Weight % 2 == 0 ? -1.0f : 1.0f); - - if (Weight < 2) - pPos->Elev = -30.0f; - - else if (Weight < 4) - pPos->Elev = 360.0f - 50.0f; - else if (Weight < 7) - pPos->Elev = 720.0f - 50.0f; - else - pPos->Elev = 360.0f * GetDefaultSpins(Weight) - 50.0f; - pPos->z = 0.0f; + 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; } - -/* Update the members of PATTERN_INFO for a given juggling pattern. The pattern - * can come from an ordinary siteswap (Site != NULL) or from a Juggle Saver - * compatible pattern that contains, position and object info etc. - * We assume that patterns are valid and have at least one object (a site of - * zeros is invalid). The ones we generate randomly are safe. */ - -static void InitPatternInfo(PATTERN_INFO* pPattern, - const int* Site, const EXT_SITE_INFO* pExtInfo, int Len) +/* 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 up on the length of the site if it's of an odd length. - * This way we can store position information: even indices are on one - * side and odds are on the other. */ - int InfoLen = Len % 2 == 1 ? Len * 2 : Len; - int i; - THROW_INFO* pInfo = (THROW_INFO*) calloc(InfoLen, sizeof(THROW_INFO)); - int Objects = 0; - unsigned char* pUsed; - - pPattern->MaxWeight = 0; - pPattern->ThrowLen = InfoLen; - pPattern->pThrowInfo = pInfo; - - SetHeightAndAlpha(pPattern, Site, pExtInfo, Len); - - /* First pass through we assign the things we know about for sure just by - * looking at the throw weight at this position. This includes TimeInAir; - * the throw and catch positions; and throw and catch velocities. - * Other information, like the total time for the throw (i.e. when the - * object is thrown again) relies on how the rest of the pattern is - * structured and we defer this task for successive passes and just make - * guesses at this stage. */ - - for (i = 0; i < InfoLen; i++) - { - float t1; - int w = pExtInfo != NULL ? pExtInfo[i % Len].Weight : Site[i % Len]; - - pInfo[i].TotalTime = pInfo[i].TimeInAir = w; - pInfo[(w + i) % Len].PrevThrow = i; - - /* work out where we are throwing this object from and where it's going - * to land. */ - - if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_FROM_POS) == 0) - GetDefaultFromPosition(i % 2, w, &pInfo[i].FromPos); - else - pInfo[i].FromPos = pExtInfo[i % Len].FromPos; - - if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_TO_POS) == 0) - GetDefaultToPosition(i % 2, w, &pInfo[i].ToPos); - else - pInfo[i].ToPos = pExtInfo[i % Len].ToPos; - - /* calculate the velocity the object is moving at the start and end - * points -- this information is used to interpolate the hand position - * and to determine how the object is moved while it's carried to the - * next throw position. - * - * The throw motion is governed by a parabola of the form: - * y(t) = a * t ^ 2 + b * t + c - * Assuming at the start of the throw y(0) = y0; when it's caught - * y(t1) = y1; and the accelation is -2.0 * alpha the equation can be - * rewritten as: - * y(t) = -alpha * t ^ 2 + (alpha * t1 + (y1 - y0) / t1) * t + y0 - * making the velocity: - * y'(t) = -2.0 * alpha * t + (alpha * t1 + (y1 - y0) / t1) - * To get the y component of velocity first we determine t1, which is - * the throw weight minus the time spent carrying the object. Then - * perform the relevant substitutions into the above. - * (note: y'(t) = y'(0) - 2.0 * alpha * t) - * - * The velocity in the x direction is constant and can be simply - * obtained from: - * x' = (x1 - x0) / t1 - * where x0 and x1 are the start and end x-positions respectively. - */ - - t1 = w - CARRY_TIME; - - pInfo[i].FromVelocity.y = pPattern->Alpha * t1 + - (pInfo[i].ToPos.y - pInfo[i].FromPos.y) / t1; - pInfo[i].ToVelocity.y = - pInfo[i].FromVelocity.y - 2.0f * pPattern->Alpha * t1; - pInfo[i].FromVelocity.x = pInfo[i].ToVelocity.x = - (pInfo[i].ToPos.x - pInfo[i].FromPos.x) / t1; - pInfo[i].FromVelocity.z = pInfo[i].ToVelocity.z = - (pInfo[i].ToPos.z - pInfo[i].FromPos.z) / t1; - pInfo[i].FromVelocity.Rot = pInfo[i].ToVelocity.Rot = - (pInfo[i].ToPos.Rot - pInfo[i].FromPos.Rot) / t1; - pInfo[i].FromVelocity.Elev = pInfo[i].ToVelocity.Elev = - (pInfo[i].ToPos.Elev - pInfo[i].FromPos.Elev) / t1; - - - if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SNATCH) != 0) - { - pInfo[i].ToVelocity.x = pExtInfo[i % Len].SnatchX; - pInfo[i].ToVelocity.y = pExtInfo[i % Len].SnatchY; - } - - if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SPINS) != 0) - { - pInfo[i].ToPos.Elev = 360.0f * pExtInfo[i % Len].Spins + - NormaliseAngle(pInfo[i].ToPos.Elev); - } - - Objects += w; - if (w > pPattern->MaxWeight) - pPattern->MaxWeight = w; - } + 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; +} - Objects /= InfoLen; +/* 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; +} - /* Now we go through again and work out exactly how long it is before the - * object is thrown again (ie. the TotalTime) typically this is the same - * as the time in air, however when we have a throw weight of '2' it's - * treated as a hold and we increase the total time accordingly. */ - for (i = 0; i < InfoLen; i++) - { - if (pInfo[i].TimeInAir != 2) - { - int Next = pInfo[i].TimeInAir + i; - while (pInfo[Next % InfoLen].TimeInAir == 2) - { - Next += 2; - pInfo[i].TotalTime += 2; - } - /* patch up the Prev index. We don't bother to see if this - * is different from before since it's always safe to reassign it */ - pInfo[Next % InfoLen].PrevThrow = i; - } - } - /* then we work our way through again figuring out where the hand goes to - * catch something as soon as it has thrown the current object. */ +#define SX 25 /* Shoulder Width */ - for (i = 0; i < InfoLen; i++) - { - if (pInfo[i].TimeInAir != 0 && pInfo[i].TimeInAir != 2) - { - /* what we're trying to calculate is how long the hand that threw - * the current object has to wait before it throws another. - * Typically this is two beats later. However '0' in the site swap - * represents a gap in a catch, and '2' represents a hold. We skip - * over these until we reach the point where a ball is actually - * thrown. */ - int Wait = 2; - while (pInfo[(i + Wait) % InfoLen].TimeInAir == 2 || - pInfo[(i + Wait) % InfoLen].TimeInAir == 0) - { - Wait += 2; - } - pInfo[i].NextForHand = Wait; - } - else - { - /* Be careful to ensure the current weight isn't one we're trying - * to step over; otherwise we could potentially end up in an - * infinite loop. The value we assign may end up being used - * in patterns with infinite gaps (e.g. 60) or infinite holds - * (e.g. 62) in both cases, setting a wait of 2 ensures things - * are well behaved. */ - pInfo[i].NextForHand = 2; - } - } +/* 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 = 0; + + /* 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; + } + +#ifdef _2DSpinsDontWorkIn3D + t->angle = (((t->hand == LEFT) ^ + (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))? + -1 : 1) * M_PI/2; +#else + t->angle = -M_PI/2; +#endif - /* Now work out the starting positions for the objects. To do this we - * unweave the initial throws so we can pick out the individual threads. */ + t->x = t->cx + ((t->hand == LEFT) ? xo : -xo); + t->y = yo; - pUsed = (unsigned char*) - malloc(sizeof(unsigned char) * pPattern->MaxWeight); - pPattern->Objects = Objects; - pPattern->pObjectInfo = (OBJECT_POSITION*) calloc( - Objects, sizeof(OBJECT_POSITION)); + /* Only mark complete if it was already linked */ + if(t->status == LINKEDACTION) { + t->status = PTHRATCH; + } + } + } +} - for (i = 0; i < pPattern->MaxWeight; i++) - pUsed[i] = 0; - for (i = 0; i < pPattern->MaxWeight; i++) - { - int w = pInfo[i % InfoLen].TimeInAir; - if (pUsed[i] == 0 && w != 0) - { - Objects--; - pPattern->pObjectInfo[Objects].TimeOffset = i; - pPattern->pObjectInfo[Objects].ThrowIndex = i % InfoLen; - pPattern->pObjectInfo[Objects].TotalTwist = 0.0f; +/* Private physics functions */ - if (pExtInfo != NULL && - pExtInfo[i % Len].ObjectType != OBJECT_DEFAULT) - { - pPattern->pObjectInfo[Objects].ObjectType = - pExtInfo[i % Len].ObjectType; - } - else - { - pPattern->pObjectInfo[Objects].ObjectType = (1 + random() % 3); - } - } +/* Compute the spin-rate for a trajectory. Different types of throw + (eg, regular thows, bounces, kicks, etc) have different spin + requirements. - if (w + i < pPattern->MaxWeight) - pUsed[w + i] = 1; - - } + 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) +{ +#ifdef _2DSpinsDontWorkIn3D + const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1; +#else + const int dir = 1; +#endif - pPattern->LeftHand.TimeOffset = pPattern->LeftHand.ThrowIndex = 0; - pPattern->RightHand.TimeOffset = pPattern->RightHand.ThrowIndex = 1; - - free(pUsed); + 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; + } } -static void ReleasePatternInfo(PATTERN_INFO* pPattern) +/* compute the angle at the end of a spinning trajectory */ +static double +end_spin(Trajectory *t) { - free(pPattern->pObjectInfo); - free(pPattern->pThrowInfo); + return t->angle + t->spin * (t->finish - t->start); } - -/***************************************************************************** - * - * Sites - * - ****************************************************************************/ - -/* Generate a random site swap. We assume that MaxWeight >= ObjCount and - * Len >= MaxWeight. */ - -static int* Generate(int Len, int MaxWeight, int ObjCount) +/* 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) { - int* Weight = (int*) calloc(Len, sizeof(int)); - int* Used = (int*) calloc(Len, sizeof(int)); - int* Options = (int*) calloc(MaxWeight + 1, sizeof(int)); - int nOpts; - int i, j; - - for (i = 0; i < Len; i++) - Weight[i] = Used[i] = -1; - - /* Pick out a unique the starting position for each object. -2 is put in - * the Used array to signify this is a starting position. */ - - while (ObjCount > 0) - { - nOpts = 0; - for (j = 0; j < MaxWeight; j++) - { - if (Used[j] == -1) - Options[nOpts++] = j; - } - - Used[Options[random() % nOpts]] = -2; - ObjCount--; - } - - /* Now work our way through the pattern moving throws into an available - * landing positions. */ - for (i = 0; i < Len; i++) - { - if (Used[i] == -1) - { - /* patch up holes in the pattern to zeros */ - Used[i] = 1; - Weight[i] = 0; - } - else - { - /* Work out the possible places where a throw can land and pick a - * weight at random. */ - int w; - nOpts = 0; - - for (j = 0 ; j <= MaxWeight; j++) - { - if (Used[(i + j) % Len] == -1) - Options[nOpts++] = j; - } - - w = Options[random() % nOpts]; - Weight[i] = w; - - /* For starting throws make position available for a throw to land. - * Because Len >= MaxWeight these positions will only be filled when - * a throw wraps around the end of the site swap and therefore we - * can guarantee the all the object threads will be tied up. */ - if (Used[i] == -2) - Used[i] = -1; - - Used[(i + w) % Len] = 1; - } - } - - free(Options); - free(Used); - return Weight; + if(ObjectDefs[t->balllink->object->type].handle == 0) { + t->balllink->angle = end_spin(n); + if(t->balllink->handlink != NULL) { +#ifdef _2DSpinsDontWorkIn3D + t->balllink->handlink->angle = t->balllink->angle + M_PI; +#else + t->balllink->handlink->angle = t->balllink->angle; +#endif + } + } } - -/* Routines to parse the Juggle Saver patterns. These routines are a bit yucky - * and make the big assumption that the patterns are well formed. This is fine - * as it stands because only 'good' ones are used but if the code is ever - * extended to read arbitrary patterns (say from a file) then these routines - * need to be beefed up. */ - -/* The position text looks something like (x,y,z[,rot[,elev]]) - * where the stuff in square brackets is optional */ - -static unsigned char ParsePositionText(const char** ppch, POS* pPos) +static double +find_bounce(jugglestruct *sp, + double yo, double yf, double yc, double tc, double cor) { - const char* pch = *ppch; - unsigned char OK; - char szTemp[32]; - char* pOut; - float* Nums[4]; - int i; - - Nums[0] = &pPos->x; - Nums[1] = &pPos->y; - Nums[2] = &pPos->Rot; - Nums[3] = &pPos->Elev; - - - while (*pch == ' ') - pch++; - - OK = *pch == '('; - - if (OK) - pch++; - - for (i = 0; OK && i < 4; i++) - { - pOut = szTemp; - while (*pch == ' ') - pch++; - while (*pch != ',' && *pch != '\0' && *pch != ')' && *pch != ' ') - *pOut++ = *pch++; - *pOut = '\0'; - - if (szTemp[0] != '\0') - *Nums[i] = (float) atof(szTemp); - - while (*pch == ' ') - pch++; - - if (i < 3) - { - if (*pch == ',') - pch++; - else if (*pch == ')') - break; - else - OK = 0; - } - } - - if (OK) - { - while (*pch == ' ') - pch++; - if (*pch == ')') - pch++; - else - OK = 0; - } - - *ppch = pch; - - return OK; + 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 EXT_SITE_INFO* ParsePattern(const char* Site, int* pLen) +static Trajectory* +new_predictor(const Trajectory *t, int start, int finish, double angle) { - const char* pch = Site; - int Len = 0; - EXT_SITE_INFO* pInfo = NULL; - unsigned char OK = 1; - - while (OK && *pch != 0) - { - EXT_SITE_INFO Info; - Info.Flags = 0; + 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; +} - while (*pch == ' ') pch++; +/* 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; +} - OK = *pch != '\0'; +/* Turn abstract hand motions into cubic splines. */ +static void +hands(jugglestruct *sp) +{ + Trajectory *t, *u, *v; - if (OK) - Info.Weight = *pch >= 'A' ? *pch + 10 - 'A' : *pch - '0'; + for (t = sp->head->next; t != sp->head; t = t->next) { + /* no throw => no velocity */ + if (t->status != BPREDICTOR) { + continue; + } - /* parse object type */ - if (OK) - { - pch++; - while (*pch == ' ') pch++; + u = t->handlink; + if (u == NULL) { /* no next catch */ + continue; + } + v = u->handlink; + if (v == NULL) { /* no next throw */ + continue; + } - if (*pch == 'b' || *pch == 'B') - { - Info.ObjectType = OBJECT_BALL; - pch++; - } - else if (*pch == 'c' || *pch == 'C') - { - Info.ObjectType = OBJECT_CLUB; - pch++; - } - else if (*pch == 'r' || *pch == 'R') - { - Info.ObjectType = OBJECT_RING; - pch++; - } - else if (*pch == 'd' || *pch == 'D') - { - Info.ObjectType = OBJECT_DEFAULT; - pch++; - } - else - { - Info.ObjectType = OBJECT_DEFAULT; - } - } + /* double spline takes hand from throw, thru catch, to + next throw */ - /* Parse from position */ - if (OK) - { - while (*pch == ' ') pch++; - if (*pch == '@') - { - pch++; - GetDefaultFromPosition(Len % 2, Info.Weight, &Info.FromPos); - Info.Flags |= HAS_FROM_POS; - OK = ParsePositionText(&pch, &Info.FromPos); - } - } + t->finish = u->start; + t->status = PREDICTOR; - /* Parse to position */ - if (OK) - { - while (*pch == ' ') pch++; - if (*pch == '>') - { - pch++; - GetDefaultToPosition(Len % 2, Info.Weight, &Info.ToPos); - Info.Flags |= HAS_TO_POS; - OK = ParsePositionText(&pch, &Info.ToPos); - } - } + u->finish = v->start; + u->status = PREDICTOR; - /* Parse snatch */ - if (OK) - { - while (*pch == ' ') pch++; - if (*pch == '/') - { - POS Snatch; - pch++; - Info.Flags |= HAS_SNATCH; - OK = ParsePositionText(&pch, &Snatch); - Info.SnatchX = Snatch.x; - Info.SnatchY = Snatch.y; - } - } - /* Parse Spins */ - if (OK) - { - while (*pch == ' ') pch++; - if (*pch == '*') - { - pch++; - OK = 0; - Info.Spins = 0; - while (*pch >= '0' && *pch <= '9') - { - OK = 1; - Info.Spins = Info.Spins * 10 + *pch - '0'; - pch++; - } - } - else - Info.Spins = GetDefaultSpins(Info.Weight); + /* FIXME: These adjustments leave a small glitch when alternating + balls and clubs. Just hope no-one notices. :-) */ - Info.Flags |= HAS_SPINS; - } + /* make sure empty hand spin matches the thrown object in case it + had a handle */ - if (OK) - { - if (pInfo == NULL) - pInfo = (EXT_SITE_INFO*) malloc(sizeof(EXT_SITE_INFO)); - else - pInfo = (EXT_SITE_INFO*) realloc(pInfo, (Len + 1) * sizeof(EXT_SITE_INFO)); + t->spin = ((t->hand == LEFT)? -1 : 1 ) * + fabs((u->angle - t->angle)/(u->start - t->start)); - pInfo[Len] = Info; - Len++; - } - } - - if (!OK && pInfo != NULL) - { - free(pInfo); - pInfo = NULL; - } + u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) * + fabs((v->angle - u->angle)/(v->start - u->start)); - *pLen = Len; + (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); - return pInfo; + t->status = PREDICTOR; + } } - -/***************************************************************************** - * - * Juggle Saver Patterns - * - ***************************************************************************** - * - * This is a selection of some of the more interesting patterns from taken - * from the Juggle Saver sites.txt file. I've only used patterns that I - * originally created. - */ - -static const char* PatternText[] = -{ - "9b@(-2.5,0,-70,40)>(2.5,0,70)*2 1b@(1,0,10)>(-1,0,-10)", - - "3B@(1,-0.4)>(2,4.2)/(-2,1)3B@(-1.8,4.4)>(-2.1,0)", - - "7c@(-2,0,-20)>(1.2,0,-5)7c@(2,0,20)>(-1.2,0,5)", - - "3b@(-0.5,0)>(1.5,0) 3b@(0.5,0)>(-1.5,0) 3r@(-2.5,3,-90,80)>(2,1,90,30)" - "3b@(0.5,0)>(-1.5,0) 3b@(-0.5,0)>(1.5,0) 3r@(2.5,3,90,80)>(-2,1,-90,30)", - - "5c@(2,1.9,10)>(-1,1,10)5c@(2,1.8,10)>(-0.5,1.6,10)/(5,-1)" - "5c@(1.6,0.2,10)>(0,-1,10)/(9,-2)5c@(-2,1.9,-10)>(1,1,-10)" - "5c@(-2,1.8,-10)>(0.5,1.6,-10)/(-5,-1)5@(-1.6,0.2,-10)>(0,-1,-10)/(-9,-2)", - - "3c@(-1.5,0,0)>(-1.5,1,0)3c@(1.5,-0.2,0)>(1.5,-0.1,0)3c@(0,-0.5,0)>(0,1,0)" - "3@(-1.5,2,0)>(-1.5,-1,0)3@(1.5,0,0)>(1.5,1,0)3@(0,0,0)>(0,-0.5,0)", - - "9c@(-2.5,0,-70,40)>(2.5,0,70)*2 1c@(1,0,10)>(-1,0,-10)*0", - - "3c@(2,0.5,60,0)>(1.5,4,60,80)/(-6,-12)" - "3c@(-2,0.5,-60,0)>(-1.5,4,-60,80)/(6,-12)", - - "3c@(-0.2,0)>(1,0)3c@(0.2,0)>(-1,0)3c@(-2.5,2,-85,30)>(2.5,2,85,40)*2 " - "3@(0.2,0)>(-1,0) 3@(-0.2,0)>(1,0) 3@(2.5,2,85,30)>(-2.5,2,-85,40)*2", - - "3c@(-0.5,-0.5,20,-30)>(2.6,4.3,60,60)/(0,1)*1 " - "3c@(1.6,5.6,60,80)>(-2.6,0,-80)*0", - - "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)" - "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)" - "5c@(-3,3.5,-65,80)>(3,2.5,65) 5c@(0.3,0,-10)>(-1.2,0,-10)" - "5@(-0.3,0,10)>(1.2,0,10) 5@(0.3,0,-10)>(-1.2,0,-10)" - "5@(-0.3,0,10)>(1.2,0,10)5@(3,3.5,65,80)>(-3,2.5,-65)" -}; - - -/***************************************************************************** - * - * Rendering - * - *****************************************************************************/ - -static const float FOV = 70.0f; -static const float BodyCol[] = {0.6f, 0.6f, 0.45f, 1.0f}; -static const float HandleCol[] = {0.45f, 0.45f, 0.45f, 1.0f}; -static const float LightPos[] = {0.0f, 200.0f, 400.0f, 1.0f}; -static const float LightDiff[] = {1.0f, 1.0f, 1.0f, 0.0f}; -static const float LightAmb[] = {0.02f, 0.02f, 0.02f, 0.0f}; -static const float ShoulderPos[3] = {0.95f, 2.1f, 1.7f}; -static const float DiffCol[] = {1.0f, 0.0f, 0.0f, 1.0f}; -static const float SpecCol[] = {1.0f, 1.0f, 1.0f, 1.0f}; - -static const float BallRad = 0.34f; -static const float UArmLen = 1.9f; -static const float LArmLen = 2.3f; - -#define DL_BALL 0 -#define DL_CLUB 1 -#define DL_RING 2 -#define DL_TORSO 3 -#define DL_FOREARM 4 -#define DL_UPPERARM 5 - -static const float AltCols[][4] = -{ - {0.0f, 0.7f, 0.0f, 1.0f}, - {0.0f, 0.0f, 0.9f, 1.0f}, - {0.0f, 0.9f, 0.9f, 1.0f}, - {0.45f, 0.0f, 0.9f, 1.0f}, - {0.9f, 0.45f, 0.0f, 1.0f}, - {0.0f, 0.45f, 0.9f, 1.0f}, - {0.9f, 0.0f, 0.9f, 1.0f}, - {0.9f, 0.9f, 0.0f, 1.0f}, - {0.9f, 0.0f, 0.45f, 1.0f}, - {0.45f, 0.15f, 0.6f, 1.0f}, - {0.9f, 0.0f, 0.0f, 1.0f}, - {0.0f, 0.9f, 0.45f, 1.0f}, -}; - -static const float Cols[][4] = +/* 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) { - {0.9f, 0.0f, 0.0f, 1.0f}, /* 0 */ - {0.0f, 0.7f, 0.0f, 1.0f}, /* 1 */ - {0.0f, 0.0f, 0.9f, 1.0f}, /* 2 */ - {0.0f, 0.9f, 0.9f, 1.0f}, /* 3 */ - {0.9f, 0.0f, 0.9f, 1.0f}, /* 4 */ - {0.9f, 0.9f, 0.0f, 1.0f}, /* 5 */ - {0.9f, 0.45f, 0.0f, 1.0f}, /* 6 */ - {0.9f, 0.0f, 0.45f, 1.0f}, /* 7 */ - {0.45f, 0.9f, 0.0f, 1.0f}, /* 8 */ - {0.0f, 0.9f, 0.45f, 1.0f}, /* 9 */ - {0.45f, 0.0f, 0.9f, 1.0f}, /* 10 */ - {0.0f, 0.45f, 0.9f, 1.0f}, /* 11 */ -}; - -static int InitGLDisplayLists(void); + 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; + } +} -static void InitGLSettings(RENDER_STATE* pState, int WireFrame) +/* NOTE: returned x, y adjusted for arm reach */ +static void +reach_arm(ModeInfo * mi, Hand side, DXPoint *p) { - memset(pState, 0, sizeof(RENDER_STATE)); - - pState->trackball = gltrackball_init (); - - if (WireFrame) - glPolygonMode(GL_FRONT, GL_LINE); - - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - - glLightfv(GL_LIGHT0, GL_POSITION, LightPos); - glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiff); - glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb); - - glEnable(GL_SMOOTH); - glEnable(GL_LIGHTING); - glEnable(GL_LIGHT0); - - glDepthFunc(GL_LESS); - glEnable(GL_DEPTH_TEST); - - glCullFace(GL_BACK); - glEnable(GL_CULL_FACE); - - pState->DLStart = InitGLDisplayLists(); + 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; } - -static void SetCamera(RENDER_STATE* pState) +#if DEBUG +/* dumps a human-readable rendition of the current state of the juggle + pipeline to stderr for debugging */ +static void +dump(jugglestruct *sp) { - /* Try to work out a sensible place to put the camera so that more or less - * the whole juggling pattern fits into the screen. We assume that the - * pattern is height limited (i.e. if we get the height right then the width - * will be OK). This is a pretty good assumption given that the screen - * tends to wider than high, and that a juggling pattern is normally much - * higher than wide. - * - * If I could draw a diagram here then it would be much easier to - * understand but my ASCII-art skills just aren't up to it. - * - * Basically we estimate a bounding volume for the juggler and objects - * throughout the pattern. We don't fully account for the fact that the - * juggler moves across the stage in an epicyclic-like motion and instead - * use the near and far planes in x-y (with z = +/- w). We also - * assume that the scene is centred at x=0, this reduces our task to finding - * a bounding rectangle. Finally we need to make an estimate of the - * height - for this we work out the max height of a standard throw or max - * weight from the pattern; we then do a bit of adjustment to account for - * a throw occurring at non-zero y values. - * - * Next we work out the best way to fit this rectangle into the perspective - * transform. Based on the angle of elevation (+ve angle looks down) and - * the FOV we can work out whether it's the near or far corners that are - * the extreme points. And then trace back from them to find the eye - * point. - * - */ - - float ElevRad = pState->CameraElev * PI / 180.0f; - float w = 3.0f; - float cy, cz; - float ey, ez; - float d; - float H = 0.0f; - int i; - float a; - - float tz, ty, ta; - float bz, by, ba; - const PATTERN_INFO* pPattern = pState->pPattern; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - for (i = 0; i < pPattern->ThrowLen; i++) - H = max(H, pPattern->pThrowInfo[i].FromPos.y); - - H += pPattern->Height; - - ElevRad = pState->CameraElev * PI / 180.0f; - - /* ta is the angle from a point on the top of the bounding area to the eye - * similarly ba is the angle from a point on the bottom. */ - ta = (pState->CameraElev - (FOV - 10.0f) / 2.0f) * PI / 180.0f; - ba = (pState->CameraElev + (FOV - 10.0f) / 2.0f) * PI / 180.0f; - - /* tz and bz hold the z location of the top and bottom extreme points. - * For the top, if the angle to the eye location is positive then the - * extreme point is with far z corner (the camera looks in -ve z). - * The logic is reserved for the bottom. */ - tz = ta >= 0.0f ? -w : w; - bz = ba >= 0.0f ? w : -w; - - ty = H; - by = -1.0f; - - /* Solve of the eye location by using a bit of geometry. - * We know the eye lies on intersection of two lines. One comes from the - * top and other from the bottom. Giving two equations: - * ez = tz + a * cos(ta) = bz + b * cos(ba) - * ey = ty + a * sin(ta) = by + b * sin(ba) - * We don't bother to solve for b and use Crammer's rule to get - * | bz-tz -cos(ba) | - * | by-ty -sin(ba) | - * a = ---------------------- - * | cos(ta) -cos(ba) | - * | sin(ta) -sin(ba) | - */ - d = cosf(ba) * sinf(ta) - cosf(ta) * sinf(ba); - a = (cosf(ba) * (by - ty) - sinf(ba) * (bz - tz)) / d; - - ey = ty + a * sinf(ta); - ez = tz + a * cosf(ta); - - /* now work back from the eye point to get the lookat location */ - cz = 0.0; - cy = ey - ez * tanf(ElevRad); - - /* use the distance from the eye to the scene centre to get a measure - * of what the far clipping should be. We then add on a bit more to be - * comfortable */ - d = sqrtf(ez * ez + (cy - ey) * (cy - ey)); - - gluPerspective(FOV, pState->AspectRatio, 0.1f, d + 20.0f); - gluLookAt(0.0, ey, ez, 0.0, cy, cz, 0.0, 1.0, 0.0); - - glMatrixMode(GL_MODELVIEW); + 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 void ResizeGL(RENDER_STATE* pState, int w, int h) +static int get_num_balls(const char *j) { - glViewport(0, 0, w, h); - pState->AspectRatio = (float) w / h; - SetCamera(pState); + 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; } - -/* Determine the angle at the vertex of a triangle given the length of the - * three sides. */ - -static double CosineRule(double a, double b, double c) +static int +compare_num_balls(const void *p1, const void *p2) { - double cosang = (a * a + b * b - c * c) / (2 * a * b); - /* If lengths don't form a proper triangle return something sensible. - * This typically happens with patterns where the juggler reaches too - * far to get hold of an object. */ - if (cosang < -1.0 || cosang > 1.0) - return 0; - else - return 180.0 * acos(cosang) / PI; + 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); + } } -/* Spheres for the balls are generated by subdividing each triangle face into - * four smaller triangles. We start with an octahedron (8 sides) and repeat the - * process a number of times. The result is a mesh that can be split into four - * panels (like beanbags) and is smoother than the normal stacks and slices - * approach. */ +/************************************************************************** + * Rendering Functions * + * * + **************************************************************************/ -static void InterpolateVertex( - const float* v1, const float* v2, float t, float* result) +static int +show_arms(ModeInfo * mi) { - result[0] = v1[0] * (1.0f - t) + v2[0] * t; - result[1] = v1[1] * (1.0f - t) + v2[1] * t; - result[2] = v1[2] * (1.0f - t) + v2[2] * t; -} + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + unsigned int i, j; + Hand side; + XPoint a[countof(sp->arm[0][0])]; + int slices = 12; + int thickness = 7; + int soffx = 10; + int soffy = 11; + + j = 1; + for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) { + /* Translate into device coords */ + for(i = 0; i < countof(a); i++) { + a[i].x = (short)(SCENE_WIDTH/2 + sp->arm[j][side][i].x*sp->scale); + a[i].y = (short)(SCENE_HEIGHT - sp->arm[j][side][i].y*sp->scale); + if(j == 1) + sp->arm[0][side][i] = sp->arm[1][side][i]; + } + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + + /* Upper arm */ + polys += tube (a[2].x - (side == LEFT ? soffx : -soffx), a[2].y + soffy, 0, + a[1].x, a[1].y, ARMLENGTH/2, + thickness, 0, slices, + True, True, MI_IS_WIREFRAME(mi)); + + /* Lower arm */ + polys += tube (a[1].x, a[1].y, ARMLENGTH/2, + a[0].x, a[0].y, ARMLENGTH, + thickness * 0.8, 0, slices, + True, True, MI_IS_WIREFRAME(mi)); + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + + /* Shoulder */ + glPushMatrix(); + glTranslatef (a[2].x - (side == LEFT ? soffx : -soffx), + a[2].y + soffy, + 0); + glScalef(9, 9, 9); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + /* Elbow */ + glPushMatrix(); + glTranslatef (a[1].x, a[1].y, ARMLENGTH/2); + glScalef(4, 4, 4); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); -static void SetGLVertex(const float* v, float rad) -{ - float Len = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + /* Hand */ + glPushMatrix(); + glTranslatef (a[0].x, a[0].y, ARMLENGTH); + glScalef(8, 8, 8); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - if (Len >= 1.0e-10f) - { - glNormal3f(v[0] / Len, v[1] / Len, v[2] / Len); - glVertex3f(rad * v[0] / Len, rad * v[1] / Len, rad * v[2] / Len); - } - else - glVertex3fv(v); + } + return polys; } - -static void SphereSegment( - const float* v1, const float* v2, const float* v3, float r, int Levels) +static int +show_figure(ModeInfo * mi, Bool init) { - int i, j; + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*XPoint p[7];*/ + int i; + + /* +-----+ 9 + | 6 | + 10 +--+--+ + 2 +---+---+ 3 + \ 5 / + \ / + \ / + 1 + + / \ + / \ + 0 +-----+ 4 + | | + | | + | | + 7 + + 8 + */ + + /* #### most of this is unused now */ + 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[countof(figure)]; + GLfloat gcolor[4] = { 1, 1, 1, 1 }; + + /* Translate into device coords */ + for(i = 0; i < countof(figure); i++) { + a[i].x = (short)(SCENE_WIDTH/2 + (sp->cx + figure[i].x)*sp->scale); + a[i].y = (short)(SCENE_HEIGHT - figure[i].y*sp->scale); + } + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor); + + { + GLfloat scale = ((GLfloat) a[10].x - a[9].x) / 2; + int slices = 12; - for (i = 0; i < Levels; i++) + glPushMatrix(); { - float A[3], B[3], C[3], D[3]; + glTranslatef(a[6].x, a[6].y - scale, 0); + glScalef(scale, scale, scale); + + /* Head */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glPushMatrix(); + scale = 0.75; + glScalef(scale, scale, scale); + glTranslatef(0, 0.3, 0); + glPushMatrix(); + glTranslatef(0, 0, 0.35); + polys += tube (0, 0, 0, + 0, 1.1, 0, + 0.64, 0, + slices, True, True, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + glScalef(0.9, 0.9, 1); + polys += unit_sphere(2*slices, 2*slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + + /* Neck */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + glTranslatef(0, 1.1, 0); + glPushMatrix(); + scale = 0.35; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + + /* Torso */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glTranslatef(0, 1.1, 0); + glPushMatrix(); + glScalef(0.9, 1.0, 0.9); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + + /* Belly */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + glTranslatef(0, 1.0, 0); + glPushMatrix(); + scale = 0.6; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + + /* Hips */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glTranslatef(0, 0.8, 0); + glPushMatrix(); + scale = 0.85; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + + + /* Legs */ + glTranslatef(0, 0.7, 0); + + for (i = -1; i <= 1; i += 2) { + glPushMatrix(); + + glRotatef (i*10, 0, 0, 1); + glTranslatef(-i*0.65, 0, 0); - InterpolateVertex(v3, v1, (float) i / Levels, D); - InterpolateVertex(v3, v1, (float)(i + 1) / Levels, A); - InterpolateVertex(v3, v2, (float)(i + 1) / Levels, B); - InterpolateVertex(v3, v2, (float) i / Levels, C); + /* Hip socket */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + scale = 0.45; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + + /* Thigh */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glPushMatrix(); + glTranslatef(0, 0.6, 0); + polys += tube (0, 0, 0, + 0, 3.5, 0, + 1, 0, + slices, True, True, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - glBegin(GL_TRIANGLE_STRIP); + /* Knee */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + glPushMatrix(); + glTranslatef(0, 4.4, 0); + scale = 0.7; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - SetGLVertex(B, r); - SetGLVertex(C, r); - - for (j = 1; j <= i; j++) - { - float v[3]; + /* Calf */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glPushMatrix(); + glTranslatef(0, 4.7, 0); + polys += tube (0, 0, 0, + 0, 4.7, 0, + 0.8, 0, + slices, True, True, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - InterpolateVertex(B, A, (float) j / (i + 1), v); - SetGLVertex(v, r); + /* Ankle */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2); + glPushMatrix(); + glTranslatef(0, 9.7, 0); + scale = 0.5; + glScalef(scale, scale, scale); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - InterpolateVertex(C, D, (float) j / i, v); - SetGLVertex(v, r); - } + /* Foot */ + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1); + glPushMatrix(); + glRotatef (-i*10, 0, 0, 1); + glTranslatef(-i*1.75, 9.7, 0.9); + + glScalef (0.4, 1, 1); + polys += tube (0, 0, 0, + 0, 0.6, 0, + 1.9, 0, + slices*4, True, True, MI_IS_WIREFRAME(mi)); + glPopMatrix(); - SetGLVertex(A, r); - - glEnd(); + glPopMatrix(); + } } + glPopMatrix(); + } + + 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; + } + } + return polys; } +typedef struct { GLfloat x, y, z; } XYZ; -/* OK, this function is a bit of misnomer, it only draws half a sphere. Indeed - * it draws two panels and allows us to colour this one way, then draw the - * same shape again rotated 90 degrees in a different colour. Resulting in what - * looks like a four-panel beanbag in two complementary colours. */ - -static void DrawSphere(float rad) +/* lifted from sphere.c */ +static int +striped_unit_sphere (int stacks, int slices, + int stripes, + GLfloat *color1, GLfloat *color2, + int wire_p) { - int Levels = 4; - float v1[3], v2[3], v3[3]; - - v1[0] = 1.0f, v1[1] = 0.0f; v1[2] = 0.0f; - v2[0] = 0.0f, v2[1] = 1.0f; v2[2] = 0.0f; - v3[0] = 0.0f, v3[1] = 0.0f; v3[2] = 1.0f; - SphereSegment(v1, v2, v3, rad, Levels); - - v2[1] = -1.0f; - SphereSegment(v2, v1, v3, rad, Levels); - - v1[0] = v3[2] = -1.0f; - SphereSegment(v2, v1, v3, rad, Levels); - - v2[1] = 1.0f; - SphereSegment(v1, v2, v3, rad, Levels); -} - + int polys = 0; + int i,j; + double theta1, theta2, theta3; + XYZ e, p; + XYZ la = { 0, 0, 0 }, lb = { 0, 0, 0 }; + XYZ c = {0, 0, 0}; /* center */ + double r = 1.0; /* radius */ + int stacks2 = stacks * 2; + + if (r < 0) + r = -r; + if (slices < 0) + slices = -slices; + + if (slices < 4 || stacks < 2 || r <= 0) + { + glBegin (GL_POINTS); + glVertex3f (c.x, c.y, c.z); + glEnd(); + return 1; + } -static void DrawRing(void) -{ - const int Facets = 22; - const float w = 0.1f; - GLUquadric* pQuad = gluNewQuadric(); - glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - glTranslatef(0.0f, 0.0f, -w / 2.0f); + glFrontFace(GL_CW); - gluCylinder(pQuad, 1.0f, 1.0f, w, Facets, 1); - gluQuadricOrientation(pQuad, GLU_INSIDE); + for (j = 0; j < stacks; j++) + { + theta1 = j * (M_PI+M_PI) / stacks2 - M_PI_2; + theta2 = (j + 1) * (M_PI+M_PI) / stacks2 - M_PI_2; - gluCylinder(pQuad, 0.7f, 0.7f, w, Facets, 1); - gluQuadricOrientation(pQuad, GLU_OUTSIDE); + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, + ((j == 0 || j == stacks-1 || + j % (stacks / (stripes+1))) + ? color1 : color2)); - glTranslatef(0.0f, 0.0f, w); - gluDisk(pQuad, 0.7, 1.0f, Facets, 1); + glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP); + for (i = 0; i <= slices; i++) + { + theta3 = i * (M_PI+M_PI) / slices; - glRotatef(180.0f, 0.0f, 1.0f, 0.0f); - glTranslatef(0.0f, 0.0f, w); - gluDisk(pQuad, 0.7, 1.0f, Facets, 1); + if (wire_p && i != 0) + { + glVertex3f (lb.x, lb.y, lb.z); + glVertex3f (la.x, la.y, la.z); + } - gluDeleteQuadric(pQuad); + e.x = cos (theta2) * cos(theta3); + e.y = sin (theta2); + e.z = cos (theta2) * sin(theta3); + p.x = c.x + r * e.x; + p.y = c.y + r * e.y; + p.z = c.z + r * e.z; + + glNormal3f (e.x, e.y, e.z); + glTexCoord2f (i / (double)slices, + 2*(j+1) / (double)stacks2); + glVertex3f (p.x, p.y, p.z); + if (wire_p) la = p; + + e.x = cos(theta1) * cos(theta3); + e.y = sin(theta1); + e.z = cos(theta1) * sin(theta3); + p.x = c.x + r * e.x; + p.y = c.y + r * e.y; + p.z = c.z + r * e.z; + + glNormal3f (e.x, e.y, e.z); + glTexCoord2f (i / (double)slices, + 2*j / (double)stacks2); + glVertex3f (p.x, p.y, p.z); + if (wire_p) lb = p; + polys++; + } + glEnd(); + } + return polys; } -/* The club follows a 'circus club' design i.e. it has stripes running down the - * body. The club is draw such that the one stripe uses the current material - * and the second stripe the standard silver colour. */ -static void DrawClub(void) +static int +show_ball(ModeInfo *mi, unsigned long color, Trace *s) { - const float r[4] = {0.06f, 0.1f, 0.34f, 0.34f / 2.0f}; - const float z[4] = {-0.4f, 0.6f, 1.35f, 2.1f}; - float na[4]; - const int n = 18; - int i, j; - GLUquadric* pQuad; - - na[0] = (float) atan((r[1] - r[0]) / (z[1] - z[0])); - na[1] = (float) atan((r[2] - r[1]) / (z[2] - z[1])); - na[2] = (float) atan((r[3] - r[1]) / (z[3] - z[1])); - na[3] = (float) atan((r[3] - r[2]) / (z[3] - z[2])); - - for (i = 0; i < n; i += 2) - { - float a1 = i * PI * 2.0f / n; - float a2 = (i + 1) * PI * 2.0f / n; - - glBegin(GL_TRIANGLE_STRIP); - for (j = 1; j < 4; j++) - { - glNormal3f(cosf(na[j]) * cosf(a1), - cosf(na[j]) * sinf(a1), sinf(na[j])); - - glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]); + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*int offset = (int)(s->angle*64*180/M_PI);*/ + short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale); + short y = (short)(SCENE_HEIGHT - s->y * sp->scale); + GLfloat gcolor1[4] = { 0, 0, 0, 1 }; + GLfloat gcolor2[4] = { 0, 0, 0, 1 }; + int slices = 24; + + /* Avoid wrapping */ + if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0; + + gcolor1[0] = mi->colors[color].red / 65536.0; + gcolor1[1] = mi->colors[color].green / 65536.0; + gcolor1[2] = mi->colors[color].blue / 65536.0; + + gcolor2[0] = gcolor1[0] / 3; + gcolor2[1] = gcolor1[1] / 3; + gcolor2[2] = gcolor1[2] / 3; + + { + GLfloat scale = BALLRADIUS; + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(scale, scale, scale); - glNormal3f(cosf(na[j]) * cosf(a2), - cosf(na[j]) * sinf(a2), sinf(na[j])); + glRotatef (s->angle / M_PI*180, 1, 1, 0); - glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]); - } - glEnd(); - } + polys += striped_unit_sphere (slices, slices, s->divisions, + gcolor1, gcolor2, MI_IS_WIREFRAME(mi)); + glPopMatrix(); + } + return polys; +} - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, HandleCol); +static int +show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s) +{ + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*int offset = (int)(s->angle*64*180/M_PI);*/ + short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale); + short y = (short)(SCENE_HEIGHT - s->y * sp->scale); + double radius = 12 * sp->scale; + GLfloat gcolor1[4] = { 0, 0, 0, 1 }; + GLfloat gcolor2[4] = { 1, 1, 1, 1 }; + int slices = 16; + int divs = s->divisions; + divs = 4; + + /* 6 6 + +-+ + / \ + 4 +-----+ 7 + ////////\ + 3 +---------+ 8 + 2 +---------+ 9 + |///////| + 1 +-------+ 10 + | | + | | + | | + | | + | | + | | + +-+ + 0 11 */ + + /* Avoid wrapping */ + if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0; + + gcolor1[0] = mi->colors[color].red / 65536.0; + gcolor1[1] = mi->colors[color].green / 65536.0; + gcolor1[2] = mi->colors[color].blue / 65536.0; + + { + GLfloat scale = radius; + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef(scale, scale, scale); - for (i = 1; i < n; i += 2) - { - float a1 = i * PI * 2.0f / n; - float a2 = (i + 1) * PI * 2.0f / n; + glTranslatef (0, 0, 2); /* put end of handle in hand */ - glBegin(GL_TRIANGLE_STRIP); - for (j = 1; j < 4; j++) - { - glNormal3f(cosf(na[j]) * cosf(a1), - cosf(na[j]) * sinf(a1), sinf(na[j])); + glRotatef (s->angle / M_PI*180, 1, 0, 0); - glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]); + glPushMatrix(); + glScalef (0.5, 1, 0.5); + polys += striped_unit_sphere (slices, slices, divs, gcolor2, gcolor1, + MI_IS_WIREFRAME(mi)); + glPopMatrix(); + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2); + polys += tube (0, 0, 0, + 0, 2, 0, + 0.2, 0, + slices, True, True, MI_IS_WIREFRAME(mi)); - glNormal3f(cosf(na[j]) * cosf(a2), - cosf(na[j]) * sinf(a2), sinf(na[j])); + glTranslatef (0, 2, 0); + glScalef (0.25, 0.25, 0.25); + polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi)); - glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]); - } - glEnd(); - } + glPopMatrix(); + } + return polys; +} - pQuad = gluNewQuadric(); - glTranslatef(0.0f, 0.0f, z[0]); - gluCylinder(pQuad, r[0], r[1], z[1] - z[0], n, 1); - glTranslatef(0.0f, 0.0f, z[3] - z[0]); - gluDisk(pQuad, 0.0, r[3], n, 1); - glRotatef(180.0f, 0.0f, 1.0f, 0.0f); - glTranslatef(0.0f, 0.0f, z[3] - z[0]); - gluDisk(pQuad, 0.0, r[0], n, 1); - gluDeleteQuadric(pQuad); +static int +show_torch(ModeInfo *mi, unsigned long color, Trace *s) +{ + int polys = 0; +#if 0 + 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 > SCENE_HEIGHT * 2) return 0; + + head.x = (short)(SCENE_WIDTH/2 + dhead.x*sp->scale); + head.y = (short)(SCENE_HEIGHT - dhead.y*sp->scale); + + last.x = (short)(SCENE_WIDTH/2 + dlast.x*sp->scale); + last.y = (short)(SCENE_HEIGHT - dlast.y*sp->scale); + + tail.x = (short)(SCENE_WIDTH/2 + + (s->x + TailLen * PERSPEC * sa)*sp->scale ); + tail.y = (short)(SCENE_HEIGHT - (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); + draw_line(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); + + draw_line(mi, head.x, head.y, last.x, last.y); + +#endif /* 0 */ + return polys; } -/* In total 6 display lists are used. There are created based on the DL_ - * constants defined earlier. The function returns the index of the first - * display list, all others can be calculated based on an offset from there. */ - -static int InitGLDisplayLists(void) +static int +show_knife(ModeInfo *mi, unsigned long color, Trace *s) { - int s = glGenLists(6); - GLUquadric* pQuad; - - glNewList(s + DL_BALL, GL_COMPILE); - DrawSphere(BallRad); - glEndList(); - - glNewList(s + DL_CLUB, GL_COMPILE); - DrawClub(); - glEndList(); - - glNewList(s + DL_RING, GL_COMPILE); - DrawRing(); - glEndList(); - - pQuad = gluNewQuadric(); - gluQuadricNormals(pQuad, GLU_SMOOTH); - - glNewList(s + DL_TORSO, GL_COMPILE); - glPushMatrix(); - glTranslatef(ShoulderPos[0], ShoulderPos[1], -ShoulderPos[2]); - glRotatef(-90.0f, 0.0f, 1.0f, 0.0f); - gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[0] * 2, 18, 1); - glPopMatrix(); - - glPushMatrix(); - glTranslatef(0.0f, -1.0f, -ShoulderPos[2]); - glRotatef(-90.0f, 1.0f, 0.0f, 0.0f); - gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[1] + 1.0f, 18, 1); - glRotatef(180.0f, 1.0f, 0.0f, 0.0f); - gluDisk(pQuad, 0.0, 0.3, 18, 1); - glPopMatrix(); - - /* draw the head */ - glPushMatrix(); - glTranslatef(0.0f, ShoulderPos[1] + 1.0f, -ShoulderPos[2]); - glRotatef(-30.0f, 1.0f, 0.0f, 0.0f); - gluCylinder(pQuad, 0.5, 0.5, 0.3, 15, 1); - - glPushMatrix(); - glRotatef(180.0f, 1.0f, 0.0f, 0.0f); - glRotatef(180.0f, 0.0f, 0.0f, 1.0f); - gluDisk(pQuad, 0.0, 0.5, 15, 1); - glPopMatrix(); - - glTranslatef(0.0f, 0.0f, .3f); - gluDisk(pQuad, 0.0, 0.5, 15, 1); - glPopMatrix(); - glEndList(); - - glNewList(s + DL_UPPERARM, GL_COMPILE); - gluQuadricNormals(pQuad, GLU_SMOOTH); - gluQuadricDrawStyle(pQuad, GLU_FILL); - gluSphere(pQuad, 0.3, 12, 8); - - gluCylinder(pQuad, 0.3, 0.3, UArmLen, 12, 1); - glTranslatef(0.0f, 0.0f, UArmLen); - gluSphere(pQuad, 0.3, 12, 8); - glEndList(); - - glNewList(s + DL_FOREARM, GL_COMPILE); - gluCylinder(pQuad, 0.3, 0.3 / 2.0f, LArmLen, 12, 1); - glTranslatef(0.0f, 0.0f, LArmLen); - gluDisk(pQuad, 0, 0.3 / 2.0f, 18, 1); - glEndList(); - - gluDeleteQuadric(pQuad); - return s; + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*int offset = (int)(s->angle*64*180/M_PI);*/ + short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale); + short y = (short)(SCENE_HEIGHT - s->y * sp->scale); + GLfloat gcolor1[4] = { 0, 0, 0, 1 }; + GLfloat gcolor2[4] = { 1, 1, 1, 1 }; + int slices = 8; + + /* Avoid wrapping */ + if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0; + + gcolor1[0] = mi->colors[color].red / 65536.0; + gcolor1[1] = mi->colors[color].green / 65536.0; + gcolor1[2] = mi->colors[color].blue / 65536.0; + + glPushMatrix(); + glTranslatef(x, y, 0); + glScalef (2, 2, 2); + + glTranslatef (0, 0, 2); /* put end of handle in hand */ + glRotatef (s->angle / M_PI*180, 1, 0, 0); + + glScalef (0.3, 1, 1); /* flatten blade */ + + glTranslatef(0, 6, 0); + glRotatef (180, 1, 0, 0); + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1); + polys += tube (0, 0, 0, + 0, 10, 0, + 1, 0, + slices, True, True, MI_IS_WIREFRAME(mi)); + + glTranslatef (0, 12, 0); + glScalef (0.7, 10, 0.7); + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2); + polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi)); + + glPopMatrix(); + return polys; } -/* Drawing the arm requires connecting the upper and fore arm between the - * shoulder and hand position. Thinking about things kinematically by treating - * the shoulder and elbow as ball joints then, provided the arm can stretch far - * enough, there's a infnite number of ways to position the elbow. Basically - * it's possible to fix and hand and shoulder and then rotate the elbow a full - * 360 degrees. Clearly human anatomy isn't like this and picking a natural - * elbow position can be complex. We chicken out and assume that poking the - * elbow out by 20 degrees from the lowest position gives a reasonably looking - * orientation. */ - -static void DrawArm(RENDER_STATE* pState, float TimePos, int Left) +static int +show_ring(ModeInfo *mi, unsigned long color, Trace *s) { - POS Pos; - float x, y, len, len2, ang, ang2; - - GetHandPosition(pState->pPattern, Left, TimePos, &Pos); + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*int offset = (int)(s->angle*64*180/M_PI);*/ + short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale); + short y = (short)(SCENE_HEIGHT - s->y * sp->scale); + double radius = 12 * sp->scale; + GLfloat gcolor1[4] = { 0, 0, 0, 1 }; + GLfloat gcolor2[4] = { 0, 0, 0, 1 }; + int slices = 24; + int i, j; + int wire_p = MI_IS_WIREFRAME(mi); + GLfloat width = M_PI * 2 / slices; + GLfloat ra = 1.0; + GLfloat rb = 0.7; + GLfloat thickness = 0.15; + + /* Avoid wrapping */ + if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0; + + gcolor1[0] = mi->colors[color].red / 65536.0; + gcolor1[1] = mi->colors[color].green / 65536.0; + gcolor1[2] = mi->colors[color].blue / 65536.0; + + gcolor2[0] = gcolor1[0] / 3; + gcolor2[1] = gcolor1[1] / 3; + gcolor2[2] = gcolor1[2] / 3; + + glPushMatrix(); + glTranslatef(0, 0, 12); /* back of ring in hand */ + + glTranslatef(x, y, 0); + glScalef(radius, radius, radius); + + glRotatef (90, 0, 1, 0); + glRotatef (s->angle / M_PI*180, 0, 0, 1); + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1); + + /* discs */ + for (j = -1; j <= 1; j += 2) + { + GLfloat z = j * thickness/2; + glFrontFace (j < 0 ? GL_CCW : GL_CW); + glNormal3f (0, 0, j*1); + glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP); + for (i = 0; i < slices + (wire_p ? 0 : 1); i++) { + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, + (i % (slices/3) ? gcolor1 : gcolor2)); + GLfloat th = i * width; + GLfloat cth = cos(th); + GLfloat sth = sin(th); + glVertex3f (cth * ra, sth * ra, z); + glVertex3f (cth * rb, sth * rb, z); + polys++; + } + glEnd(); + } - x = Pos.x + (Left ? -ShoulderPos[0] : ShoulderPos[0]); - y = Pos.y - ShoulderPos[1]; + /* outer ring */ + glFrontFace (GL_CCW); + glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP); + for (i = 0; i < slices + (wire_p ? 0 : 1); i++) + { + GLfloat th = i * width; + GLfloat cth = cos(th); + GLfloat sth = sin(th); + glNormal3f (cth, sth, 0); + glVertex3f (cth * ra, sth * ra, thickness/2); + glVertex3f (cth * ra, sth * ra, -thickness/2); + polys++; + } + glEnd(); + /* inner ring */ + glFrontFace (GL_CW); + glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP); + for (i = 0; i < slices + (wire_p ? 0 : 1); i++) + { + GLfloat th = i * width; + GLfloat cth = cos(th); + GLfloat sth = sin(th); + glNormal3f (-cth, -sth, 0); + glVertex3f (cth * rb, sth * ra, thickness/2); + glVertex3f (cth * rb, sth * ra, -thickness/2); + polys++; + } + glEnd(); - len = sqrtf(x * x + y * y + ShoulderPos[2] * ShoulderPos[2]); - len2 = sqrtf(x * x + ShoulderPos[2] * ShoulderPos[2]); + glFrontFace (GL_CCW); + glPopMatrix(); + return polys; +} - ang = (float) CosineRule(UArmLen, len, LArmLen); - ang2 = (float) CosineRule(UArmLen, LArmLen, len); - if (ang == 0.0 && ang2 == 0) - ang2 = 180.0; +static int +show_bball(ModeInfo *mi, unsigned long color, Trace *s) +{ + int polys = 0; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + /*int offset = (int)(s->angle*64*180/M_PI);*/ + short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale); + short y = (short)(SCENE_HEIGHT - s->y * sp->scale); + double radius = 12 * sp->scale; + GLfloat gcolor1[4] = { 0, 0, 0, 1 }; + GLfloat gcolor2[4] = { 0, 0, 0, 1 }; + int slices = 16; + int i, j; + + /* Avoid wrapping */ + if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0; + + gcolor1[0] = mi->colors[color].red / 65536.0; + gcolor1[1] = mi->colors[color].green / 65536.0; + gcolor1[2] = mi->colors[color].blue / 65536.0; + + { + GLfloat scale = radius; + glPushMatrix(); + glTranslatef(0, -6, 5); /* position on top of hand */ + + glTranslatef(x, y, 0); + glScalef(scale, scale, scale); + glRotatef (s->angle / M_PI*180, 1, 0, 1); + + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1); + polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi)); + + glRotatef (90, 0, 0, 1); + glTranslatef (0, 0, 0.81); + glScalef(0.15, 0.15, 0.15); + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2); + for (i = 0; i < 3; i++) { + glPushMatrix(); + glTranslatef (0, 0, 1); + glRotatef (360 * i / 3, 0, 0, 1); + glTranslatef (2, 0, 0); + glRotatef (18, 0, 1, 0); + glBegin (MI_IS_WIREFRAME(mi) ? GL_LINE_LOOP : GL_TRIANGLE_FAN); + glVertex3f (0, 0, 0); + for (j = slices; j >= 0; j--) { + GLfloat th = j * M_PI*2 / slices; + glVertex3f (cos(th), sin(th), 0); + polys++; + } + glEnd(); + glPopMatrix(); + } - glPushMatrix(); - glTranslatef(Left ? ShoulderPos[0] : -ShoulderPos[0], ShoulderPos[1], - -ShoulderPos[2]); - glRotatef((float)(180.0f * asin(x / len2) / 3.14f), 0.0f, 1.0f, 0.0f); - glRotatef((float)(-180.f * asin(y / len) / 3.14), 1.0f, 0.0f, 0.0f); - glRotatef(Left ? 20.0f : -20.0f, 0.0f, 0.0f, 1.0f); - glRotatef((float) ang, 1.0f, 0.0f, 0.0f); - glCallList(DL_UPPERARM + pState->DLStart); - - glRotatef((float)(ang2 - 180.0), 1.0f, 0.0f, 0.f); - glCallList(DL_FOREARM + pState->DLStart); glPopMatrix(); + } + return polys; } -static void DrawGLScene(RENDER_STATE* pState) -{ - float Time = pState->Time; - int nCols = sizeof(Cols) / sizeof(Cols[0]); - int i; +/************************************************************************** + * Public Functions * + * * + **************************************************************************/ - PATTERN_INFO* pPattern = pState->pPattern; - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); +ENTRYPOINT 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; + } +} - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glTranslatef(5.0f * sinf(pState->TranslateAngle), 0.0f, 0.0f); +/* 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 */ - gltrackball_rotate (pState->trackball); +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(sp)) + return; + + lob(mi); + + clap(sp); + + positions(sp); + + if (!projectile(sp)) { + free_juggle(sp); + return; + } + + hands(sp); +#ifdef DEBUG + if(MI_IS_DEBUG(mi)) dump(sp); +#endif +} - glRotatef(pState->SpinAngle, 0.0f, 1.0f, 0.0f); - glTranslatef(0.0, 0.0, -1.0f); +static void +change_juggle(ModeInfo * mi) +{ + jugglestruct *sp = NULL; + Trajectory *t; - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, DiffCol); - glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SpecCol); - glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 60.0f); + if (juggles == NULL) + return; + sp = &juggles[MI_SCREEN(mi)]; - for (i = 0; i < pPattern->Objects; i++) - { - POS ObjPos; - - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Cols[i % nCols]); - glPushMatrix(); + /* 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); + } + } - switch (pPattern->pObjectInfo[i].ObjectType) - { - case OBJECT_CLUB: - GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos); - glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z); - glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f); - glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f); - glTranslatef(0.0f, 0.0f, -1.0f); - glCallList(DL_CLUB + pState->DLStart); - break; - - case OBJECT_RING: - GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos); - glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z); - glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f); - glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f); - glCallList(DL_RING + pState->DLStart); - break; - - default: - GetObjectPosition(pPattern, i, Time, 0.0f, &ObjPos); - glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z); - glRotatef(ObjPos.Rot, 0.6963f, 0.6963f, 0.1742f); - glCallList(DL_BALL + pState->DLStart); - glRotatef(90.0f, 0.0f, 1.0f, 0.0f); - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, - AltCols[i % nCols]); - glCallList(DL_BALL + pState->DLStart); - break; - } + /* Pick the current object theme */ + sp->objtypes = choose_object(); - glPopMatrix(); - } + refill_juggle(mi); - glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, BodyCol); - glCallList(DL_TORSO + pState->DLStart); - DrawArm(pState, Time, 1); - DrawArm(pState, Time, 0); + mi->polygon_count += show_figure(mi, True); } -static int RandInRange(int Min, int Max) +ENTRYPOINT void +reshape_juggle (ModeInfo *mi, int width, int height) { - return Min + random() % (1 + Max - Min); -} + GLfloat h = (GLfloat) height / (GLfloat) width; + glViewport (0, 0, (GLint) width, (GLint) height); -static void UpdatePattern( - RENDER_STATE* pState, int MinBalls, int MaxBalls, - int MinHeightInc, int MaxHeightInc) -{ - if (pState->pPattern != NULL) - ReleasePatternInfo(pState->pPattern); - - pState->pPattern = (PATTERN_INFO*) malloc(sizeof(PATTERN_INFO)); - - if ((random() % 3) == 1) - { - int ExtSiteLen; - int n = random() % (sizeof(PatternText) / sizeof(PatternText[0])); - EXT_SITE_INFO* pExtInfo = ParsePattern(PatternText[n], &ExtSiteLen); - InitPatternInfo(pState->pPattern, NULL, pExtInfo, ExtSiteLen); - free(pExtInfo); - } - else - { - int* pRand; - int ballcount, maxweight; - const int RandPatternLen = 1500; - - ballcount = RandInRange(MinBalls, MaxBalls); - maxweight = ballcount + RandInRange(MinHeightInc, MaxHeightInc); - - pRand = Generate(RandPatternLen, maxweight, ballcount); - InitPatternInfo(pState->pPattern, pRand, NULL, RandPatternLen); - free(pRand); - } - - pState->CameraElev = 50.0f - random() % 90; - pState->TranslateAngle = random() % 360; - pState->SpinAngle = random() % 360; - pState->Time = 50.0f; - SetCamera(pState); -} - + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective (30.0, 1/h, 1.0, 100.0); -/******************************************************************************* - * - * XScreenSaver Configuration - * - ******************************************************************************/ + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt( 0.0, 0.0, 30.0, + 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0); -typedef struct -{ - GLXContext* glxContext; - RENDER_STATE RenderState; - float CurrentFrameRate; - unsigned FramesSinceSync; - unsigned LastSyncTime; -} JUGGLER3D_CONFIG; - - -#define DEF_MAX_OBJS "8" -#define DEF_MIN_OBJS "3" -#define DEF_MAX_HINC "6" -#define DEF_MIN_HINC "2" -#define DEF_JUGGLE_SPEED "2.2" -#define DEF_TRANSLATE_SPEED "0.1" -#define DEF_SPIN_SPEED "20.0" - -static JUGGLER3D_CONFIG* pConfigInfo = NULL; -static int MaxObjects; -static int MinObjects; -static int MaxHeightInc; -static int MinHeightInc; -static float SpinSpeed; -static float TranslateSpeed; -static float JuggleSpeed; - -static XrmOptionDescRec opts[] = -{ - {"-spin", ".spinSpeed", XrmoptionSepArg, 0}, - {"-trans", ".translateSpeed", XrmoptionSepArg, 0}, - {"-speed", ".juggleSpeed", XrmoptionSepArg, 0}, - {"-maxobjs", ".maxObjs", XrmoptionSepArg, 0}, - {"-minobjs", ".minObjs", XrmoptionSepArg, 0}, - {"-maxhinc", ".maxHinc", XrmoptionSepArg, 0}, - {"-minhinc", ".minHinc", XrmoptionSepArg, 0}, -}; + glClear(GL_COLOR_BUFFER_BIT); +} -static argtype vars[] = +ENTRYPOINT void +init_juggle (ModeInfo * mi) { - {&MaxObjects, "maxObjs", "MaxObjs", DEF_MAX_OBJS, t_Int}, - {&MinObjects, "minObjs", "MinObjs", DEF_MIN_OBJS, t_Int}, - {&MaxHeightInc, "maxHinc", "MaxHinc", DEF_MAX_HINC, t_Int}, - {&MinHeightInc, "minHinc", "MinHinc", DEF_MIN_HINC, t_Int}, - {&JuggleSpeed, "juggleSpeed", "JuggleSpeed", DEF_JUGGLE_SPEED, t_Float}, - {&TranslateSpeed, "translateSpeed", "TranslateSpeed", DEF_TRANSLATE_SPEED, t_Float}, - {&SpinSpeed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float}, -}; - + jugglestruct *sp = 0; + int wire = MI_IS_WIREFRAME(mi); + + if (!juggles) { + juggles = (jugglestruct *) + calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct)); + if (!juggles) { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + } -ENTRYPOINT ModeSpecOpt juggler3d_opts = {countof(opts), opts, countof(vars), vars}; + sp = &juggles[MI_SCREEN(mi)]; + sp->glx_context = init_GL(mi); -ENTRYPOINT void reshape_juggler3d(ModeInfo *mi, int width, int height) -{ - JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)]; - ResizeGL(&pConfig->RenderState, width, height); -} + load_font (mi->dpy, "titleFont", &sp->mode_font, &sp->font_dlist); + reshape_juggle (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); -ENTRYPOINT void init_juggler3d(ModeInfo* mi) -{ - JUGGLER3D_CONFIG* pConfig; - - if (pConfigInfo == NULL) + if (!wire) { - /* Apply suitable bounds checks to the input parameters */ - MaxObjects = max(3, min(MaxObjects, 36)); - MinObjects = max(3, min(MinObjects, MaxObjects)); - - MaxHeightInc = max(1, min(MaxHeightInc, 32)); - MinHeightInc = max(1, min(MinHeightInc, MaxHeightInc)); - - pConfigInfo = (JUGGLER3D_CONFIG*) calloc( - MI_NUM_SCREENS(mi), sizeof(JUGGLER3D_CONFIG)); - if (pConfigInfo == NULL) - { - fprintf(stderr, "%s: out of memory\n", progname); - exit(1); - } + GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0}; + GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0}; + GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; + GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0}; + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + glLightfv(GL_LIGHT0, GL_POSITION, pos); + glLightfv(GL_LIGHT0, GL_AMBIENT, amb); + glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); + glLightfv(GL_LIGHT0, GL_SPECULAR, spc); } - - pConfig = &pConfigInfo[MI_SCREEN(mi)]; - pConfig->glxContext = init_GL(mi); - pConfig->CurrentFrameRate = 0.0f; - pConfig->FramesSinceSync = 0; - pConfig->LastSyncTime = 0; - InitGLSettings(&pConfig->RenderState, MI_IS_WIREFRAME(mi)); - - UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, - MinHeightInc, MaxHeightInc); - - reshape_juggler3d(mi, MI_WIDTH(mi), MI_HEIGHT(mi)); -} - -ENTRYPOINT void draw_juggler3d(ModeInfo* mi) -{ - JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)]; - Display* pDisplay = MI_DISPLAY(mi); - Window hwnd = MI_WINDOW(mi); - - if (pConfig->glxContext == NULL) - return; - - glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pConfig->glxContext)); - - /* While drawing, keep track of the rendering speed so we can adjust the - * animation speed so things appear consistent. The basis of the this - * code comes from the frame rate counter (fps.c) but has been modified - * so that it reports the initial frame rate earlier (after 0.02 secs - * instead of 1 sec). */ - - if (pConfig->FramesSinceSync >= 1 * (int) pConfig->CurrentFrameRate) - { - struct timeval tvnow; - unsigned now; - - # ifdef GETTIMEOFDAY_TWO_ARGS - struct timezone tzp; - gettimeofday(&tvnow, &tzp); - # else - gettimeofday(&tvnow); - # endif - - now = (unsigned) (tvnow.tv_sec * 1000000 + tvnow.tv_usec); - if (pConfig->FramesSinceSync == 0) - { - pConfig->LastSyncTime = now; - } - else - { - unsigned Delta = now - pConfig->LastSyncTime; - if (Delta > 20000) - { - pConfig->LastSyncTime = now; - pConfig->CurrentFrameRate = - (pConfig->FramesSinceSync * 1.0e6f) / Delta; - pConfig->FramesSinceSync = 0; - } - } - } - - pConfig->FramesSinceSync++; - - if (pConfig->RenderState.Time > 150.0f) - { - UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, - MinHeightInc, MaxHeightInc); - } - DrawGLScene(&pConfig->RenderState); - - if (pConfig->CurrentFrameRate > 1.0e-6f) - { - pConfig->RenderState.Time += JuggleSpeed / pConfig->CurrentFrameRate; - pConfig->RenderState.SpinAngle += SpinSpeed / pConfig->CurrentFrameRate; - pConfig->RenderState.TranslateAngle += - TranslateSpeed / pConfig->CurrentFrameRate; + make_random_colormap (0, 0, 0, + mi->colors, &MI_NPIXELS(mi), + True, False, 0, False); + + { + double spin_speed = 0.05; + double wander_speed = 0.001; + double spin_accel = 0.05; + sp->rot = make_rotator (0, spin_speed, 0, + spin_accel, wander_speed, False); + sp->trackball = gltrackball_init (); + } + + 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 (mi->fps_p) - do_fps(mi); - - glFinish(); - glXSwapBuffers(pDisplay, hwnd); -} + } + + /* #### hard to make this look good in OpenGL... */ + torches = False; + + + 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); + } + + mi->polygon_count += + show_figure(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; + } + + 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 = countof(portfolio); + int numpat = 0; + int i; + + /* 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); + + /* 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(SCENE_HEIGHT/480.0, SCENE_WIDTH/160.0); +} -ENTRYPOINT Bool juggler3d_handle_event(ModeInfo* mi, XEvent* pEvent) +ENTRYPOINT Bool +juggle_handle_event (ModeInfo *mi, XEvent *event) { - JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)]; - RENDER_STATE* pState = &pConfig->RenderState; + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; - if (pEvent->xany.type == ButtonPress && - pEvent->xbutton.button == Button1) + if (event->xany.type == ButtonPress && + event->xbutton.button == Button1) { - pState->button_down_p = True; - gltrackball_start (pState->trackball, - pEvent->xbutton.x, pEvent->xbutton.y, + sp->button_down_p = True; + gltrackball_start (sp->trackball, + event->xbutton.x, event->xbutton.y, MI_WIDTH (mi), MI_HEIGHT (mi)); return True; } - else if (pEvent->xany.type == ButtonRelease && - pEvent->xbutton.button == Button1) + else if (event->xany.type == ButtonRelease && + event->xbutton.button == Button1) { - pState->button_down_p = False; + sp->button_down_p = False; return True; } - else if (pEvent->xany.type == ButtonPress && - (pEvent->xbutton.button == Button4 || - pEvent->xbutton.button == Button5 || - pEvent->xbutton.button == Button6 || - pEvent->xbutton.button == Button7)) + else if (event->xany.type == ButtonPress && + (event->xbutton.button == Button4 || + event->xbutton.button == Button5 || + event->xbutton.button == Button6 || + event->xbutton.button == Button7)) { - gltrackball_mousewheel (pState->trackball, pEvent->xbutton.button, 2, - !pEvent->xbutton.state); + gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10, + !!event->xbutton.state); return True; } - else if (pEvent->xany.type == MotionNotify && - pState->button_down_p) + else if (event->xany.type == MotionNotify && + sp->button_down_p) { - gltrackball_track (pState->trackball, - pEvent->xmotion.x, pEvent->xmotion.y, + gltrackball_track (sp->trackball, + event->xmotion.x, event->xmotion.y, MI_WIDTH (mi), MI_HEIGHT (mi)); return True; } - else if (pEvent->xany.type == KeyPress) + else if (event->xany.type == KeyPress) { - char str[20]; - KeySym Key = 0; - int count = XLookupString(&pEvent->xkey, str, 20, &Key, 0); - str[count] = '\0'; - if (*str == ' ') + KeySym keysym; + char c = 0; + XLookupString (&event->xkey, &c, 1, &keysym, 0); + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { - UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, - MinHeightInc, MaxHeightInc); + change_juggle (mi); + return True; } } - - return False; + + + return False; } -XSCREENSAVER_MODULE ("Juggler3D", juggler3d) +ENTRYPOINT void +draw_juggle (ModeInfo *mi) +{ + jugglestruct *sp = &juggles[MI_SCREEN(mi)]; + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + + Trajectory *traj = NULL; + Object *o = NULL; + unsigned long future = 0; + char *pattern = NULL; + + if (!sp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context)); + + glShadeModel(GL_SMOOTH); + + glEnable(GL_DEPTH_TEST); + glEnable(GL_NORMALIZE); + glEnable(GL_CULL_FACE); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix (); + + glTranslatef(0,-3,0); + + { + double x, y, z; + get_position (sp->rot, &x, &y, &z, !sp->button_down_p); + glTranslatef((x - 0.5) * 8, + (y - 0.5) * 3, + (z - 0.5) * 15); + + gltrackball_rotate (sp->trackball); + + get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p); + + if (y < 0.8) y = 0.8 - (y - 0.8); /* always face forward */ + if (y > 1.2) y = 1.2 - (y - 1.2); + + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); + } + + { + GLfloat scale = 20.0 / SCENE_HEIGHT; + glScalef(scale, scale, scale); + } + + glRotatef (180, 0, 0, 1); + glTranslatef(-SCENE_WIDTH/2, -SCENE_HEIGHT/2, 0); + glTranslatef(0, -150, 0); + + mi->polygon_count = 0; + + /* 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 0 + /* #### not sure what this is doing, but I'm guessing + that the use of PERSPEC means this isn't needed + in the OpenGL version? -jwz + */ + 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 +#endif + { + /* 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); + } + } + + + mi->polygon_count += show_figure(mi, False); + mi->polygon_count += show_arms(mi); + + /* Draw Objects */ + glTranslatef(0, 0, ARMLENGTH); + for (o = sp->objects->next; o != sp->objects; o = o->next) { + if(o->active) { + mi->polygon_count += ObjectDefs[o->type].draw(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 ) { + 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) { + print_gl_string (mi->dpy, sp->mode_font, sp->font_dlist, + mi->xgwa.width, mi->xgwa.height, + 10, mi->xgwa.height - 10, + sp->pattern, False); + } + +#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); + } + + glPopMatrix (); + + if (mi->fps_p) do_fps (mi); + glFinish(); + + glXSwapBuffers(dpy, window); +} + +XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle) + +#endif /* USE_GL */