http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / hacks / glx / juggler3d.c
1 /* Juggler3D, Copyright (c) 2005 Brian Apps <brian@jugglesaver.co.uk>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that copyright
6  * notice and this permission notice appear in supporting documentation.  No
7  * representations are made about the suitability of this software for any
8  * purpose.  It is provided "as is" without express or implied warranty. */
9
10 #include <X11/Intrinsic.h>
11
12 #undef countof
13 #define countof(x) (sizeof((x))/sizeof((*x)))
14
15 #define PROGCLASS          "Juggler3D"
16 #define HACK_INIT          Juggler3D_HackInitEvent
17 #define HACK_DRAW          Juggler3D_HackDrawEvent
18 #define HACK_RESHAPE       Juggler3D_HackReshapeEvent
19 #define HACK_HANDLE_EVENT  Juggler3D_HackHandleEvent
20 #define EVENT_MASK         PointerMotionMask
21 #define SWITCH_OPTS        xlockmore_opts
22
23 #define DEFAULTS    \
24     "*JuggleSpeed: 0.15\n*delay: 20000\n*showFPS: False\n*wireframe: False\n"
25
26 #include "xlockmore.h"
27 #include "gltrackball.h"
28
29 #ifdef USE_GL /* whole file */
30 #include <GL/glu.h>
31
32 /* A selection of macros to make functions from math.h return single precision
33  * numbers.  Arguably it's better to work at a higher precision and cast it
34  * back but littering the code with casts makes it less readable -- without
35  * the casts you can get tons of warnings from the compiler (particularily
36  * MSVC which enables loss of precision warnings by default) */
37  
38 #define cosf(a) (float)(cos((a)))
39 #define sinf(a) (float)(sin((a)))
40 #define tanf(a) (float)(tan((a)))
41 #define sqrtf(a) (float)(sqrt((a)))
42 #define powf(a, b) (float)(pow((a), (b)))
43
44 #undef max
45 #undef min
46
47 #define max(a, b) ((a) > (b) ? (a) : (b))
48 #define min(a, b) ((a) < (b) ? (a) : (b))
49
50
51 /******************************************************************************
52  *
53  * The code is broadly split into the following parts:
54  *
55  *  - Engine.  The process of determining the position of the juggler and 
56  *        objects being juggled at an arbitrary point in time.  This is
57  *        independent from any drawing code.
58  *  - Sites.  The process of creating a random site swap pattern or parsing
59  *        a Juggle Saver compatible siteswap for use by the engine.  For an
60  *        introduction to juggling site swaps check out
61  *         http://www.jugglingdb.com/
62  *  - Rendering.  OpenGL drawing code that animates the juggler.
63  *  - XScreenSaver.  Interface code to get thing working as a GLX hack.
64  *  
65  *****************************************************************************/
66
67
68 /*****************************************************************************
69  *
70  * Data structures
71  *
72  *****************************************************************************/
73
74 /* POS is used to represent the position of a hand when it catches or throws
75  * an object; as well as the orientation of the object.  The rotation and
76  * elevation are specified in degrees.  These angles are not normalised so that
77  * it is possible to specify how the object spins and rotates as it is thrown
78  * from the 'From' position to the 'To' position.
79  * 
80  * Because this is the position of the hand some translation is required with
81  * rings and clubs to get the centre of rotation position. */
82
83 typedef struct
84 {
85     float x;
86     float y;
87     float z;
88     float Rot;
89     float Elev;
90 } POS;
91
92
93 /* An array of THROW_INFOs are configured with each entry corresponding to the
94  * position in the site swap (In fact we double up odd length patterns to ensure
95  * there is left/right symmetry).  It allows us to quickly determine where an
96  * object and the hands are at a given time.  The information is specified in
97  * terms of throws, and positions where throws aren't present (0's and 2's) are
98  * simply ignored.
99  * 
100  * TotalTime - The count of beats before this object is thrown again.  Typically
101  *    this is the same as the weight of the throw but where an object is held it
102  *    is longer.  e.g. the first throw of the site 64242.7. will be 10, 6 for
103  *    throw and 4 (two 2's) for the carry.
104  * TimeInAir - The weight of the throw.
105  * PrevThrow - zero based index into array of THROW_INFOs of the previous throw.
106  *     e.g. for the throw '8' in the site 345678..... the PrevThrow is 1
107  *    (i.e. the 4)
108  * FromPos, FromVelocity, ToPos, ToVelocity - The position and speeds at the
109  *    start and end of the throw.  These are used to generate a spline while
110  *    carrying an object and while moving the hand from a throw to a catch.
111  * NextForHand - Number of beats before the hand that throws this object will
112  *    throw another object.  This is always going to be at least 2.  When there
113  *    are gaps in the pattern (0's) or holds (2's) NextForHand increases. */
114
115 typedef struct
116 {
117     int TotalTime;
118     int TimeInAir;
119     int PrevThrow;
120
121     POS FromPos;
122     POS FromVelocity;
123     POS ToPos;
124     POS ToVelocity;
125
126     int NextForHand;
127 } THROW_INFO;
128
129
130 /* OBJECT_POSITION works with the array of THROW_INFOs to allow us to determine
131  * exactly where an object or hand is.
132  *
133  * TimeOffset - The total number of beats expired when the object was thrown.
134  * ThrowIndex - The zero based index into the THROW_INFO array for the current
135  *     throw.
136  * ObjectType - One of the OBJECT_XX defines.
137  * TotalTwist - Only relevant for OBJECT_BALL, this is the total amount the ball
138  *     has twisted while in the air.  When segmented balls are drawn you see a 
139  *     spinning effect similar to what happens when you juggle beanbags.  */
140
141 #define OBJECT_DEFAULT 0
142 #define OBJECT_BALL 1
143 #define OBJECT_CLUB 2
144 #define OBJECT_RING 3
145
146 typedef struct
147 {
148     int TimeOffset;
149     int ThrowIndex;
150     float TotalTwist;
151     int ObjectType;
152 } OBJECT_POSITION;
153
154
155 /* PATTERN_INFO is the main structure that holds the information about a 
156  * juggling pattern. 
157  *
158  * pThrowInfo is an array of ThrowLen elements that describes each throw in the
159  *     pattern.
160  * pObjectInfo gives the current position of all objects at a given instant.
161  *     These values are updated as the pattern is animated.
162  * LeftHand and RightHand describe the current positions of each of the 
163  *     juggler's hands.
164  * MaxWeight is the maximum weight of the all throws in pThrowInfo.
165  * Height and Alpha are parameters that describe how objects fall under the
166  *     influence of gravity.  See SetHeightAndAlpha() for the gory details. */
167
168 typedef struct
169 {
170     THROW_INFO* pThrowInfo;
171     int ThrowLen;
172     
173     OBJECT_POSITION* pObjectInfo;
174     int Objects;
175
176     OBJECT_POSITION LeftHand;
177     OBJECT_POSITION RightHand;
178     
179     int MaxWeight;
180
181     float Height;
182     float Alpha;
183 } PATTERN_INFO;
184
185
186 /* EXT_SITE_INFO is used to initialise a PATTERN_INFO object using a Juggle
187  * Saver compatible site swap.  These contain additional information about the
188  * type of object thrown, the positions of throw and catch etc. */
189
190 #define HAS_FROM_POS 1
191 #define HAS_TO_POS 2
192 #define HAS_SNATCH 4
193 #define HAS_SPINS 8
194
195 typedef struct
196 {
197     unsigned Flags;
198     int Weight;
199     int ObjectType;
200     POS FromPos;
201     POS ToPos;
202     float SnatchX;
203     float SnatchY;
204     int Spins;
205 } EXT_SITE_INFO;
206
207
208 /* RENDER_STATE is used to co-ordinate the OpenGL rendering of the juggler and
209  * objects:
210  * pPattern - The pattern to be juggled
211  * CameraElev - The elevation angle (in degrees) that the camera is looking
212  *    along.  0 is horizontal and a +ve angle is looking down.  This value
213  *    should be between -90 and +90.
214  * AspectRatio - Window width to height ratio.
215  * DLStart - The number for the first display list created, any others directly
216  *    follow this.
217  * Time - Animation time (in beats)
218  * TranslateAngle - Cumulative translation (in degrees) for the juggling figure.
219  * SpinAngle- Cumulative spin (in degrees) for the juggling figure.
220  */
221
222 typedef struct
223 {
224     PATTERN_INFO* pPattern;
225     float CameraElev;
226     float AspectRatio;
227     int DLStart;
228     
229     float Time;
230     float TranslateAngle;
231     float SpinAngle;
232     
233     trackball_state *trackball;
234     Bool button_down_p;
235
236 } RENDER_STATE;
237
238
239 /*****************************************************************************
240  *
241  * Engine
242  *
243  ****************************************************************************
244  *
245  * The main purpose of the engine is to work out the exact position of all the
246  * juggling objects and the juggler's hands at any point in time.  The motion
247  * of the objects can be split into two parts: in the air and and being carried.
248  *
249  * While in the air, the motion is governed by a standard parabolic trajectory.
250  * The only minor complication is that the engine has no fixed concept of
251  * gravity, instead it using a term called Alpha that varies according to the
252  * pattern (see SetHeightAndAlpha). 
253  *
254  * The motion while an object is carried comes from fitting a spline through the
255  * catch and throw points and maintaining the catch and throw velocities at
256  * each end.  In the simplest case this boils down to cubic Bezier spline.  The
257  * only wrinkle occurs when a ball is being carried for a long time.  The simple 
258  * cubic spline maths produces a curve that goes miles away -- here we do a
259  * bit of reparameterisation so things stay within sensible bounds.
260  * (On a related note, this scheme is _much_ simpler than the Juggle Saver
261  * one.  Juggle Saver achieves 2nd order continuity and much care is taken
262  * to avoid spline ringing.)
263  * 
264  * The motion of the hands is identical to the ball carrying code. It uses two
265  * splines: one while an object is being carried; and another when it moves from
266  * the previous throw to the next catch.
267  */
268  
269 const float CARRY_TIME = 0.56f;
270 const float PI = 3.14159265358979f;
271
272
273 /* While a ball is thrown it twists slighty about an axis, this routine gives
274  * the total about of twist for a given ball throw. */
275  
276 static float GetBallTwistAmount(const THROW_INFO* pThrow)
277 {
278     if (pThrow->FromPos.x > pThrow->ToPos.x)
279         return 18.0f * powf(pThrow->TimeInAir, 1.5);
280     else
281         return -18.0f * powf(pThrow->TimeInAir, 1.5);
282 }
283
284
285 float NormaliseAngle(float Ang)
286 {
287     if (Ang >= 0.0f)
288     {
289         int i = (int) (Ang + 180.0f) / 360;
290         return Ang - 360.0f * i;
291     }
292     else
293     {
294         int i = (int)(180.0f - Ang) / 360;
295         return Ang + i * 360.0f;
296     }
297 }
298
299
300 /* The interpolate routine for ball carrying and hand motion.  We are given the
301  * start (P0) and end (P1) points and the velocities at these points, the task
302  * is to form a function P(t) such that:
303  *    P(0) = P0
304  *    P(TLen) = P1
305  *    P'(0) = V0
306  *    P'(TLen) = V1
307  */
308
309 static POS InterpolatePosition(
310     const POS* pP0, const POS* pV0, const POS* pP1, const POS* pV1,
311     float TLen, float t)
312 {
313     POS p;
314     float a, b, c, d, tt, tc;
315     
316     /* The interpolation is based on a simple cubic that achieves 1st order
317      * continuity at the end points.  However the spline can become too long if
318      * the TLen parameter is large.  In this case we cap the curve's length (fix
319      * the shape) and then reparameterise time to achieve the continuity
320      * conditions. */
321
322     tc = CARRY_TIME;
323     
324     if (TLen > tc)
325     {
326         /* The reparameterisation tt(t) gives:
327          *  tt(0) = 0, tt(TLen) = tc, tt'(0) = 1, tt'(TLen) = 1
328          * and means we can set t = tt(t), TLen = tc and then fall through
329          * to use the normal cubic spline fit.
330          *    
331          * The reparameterisation is based on two piecewise quadratics, one
332          * that goes from t = 0 to t = TLen / 2 and the other, mirrored in
333          * tt and t that goes from t = TLen / 2 to t = TLen.
334          * Because TLen > tc we can arrange for tt to be unique in the range if
335          * we specify the quadratic in tt.  i.e. t = A * tt ^ 2 + B * tt + C.
336          *
337          * Considering the first piece and applying initial conditions.
338          *   tt = 0 when t = 0   =>  C = 0
339          *   tt' = 1 when t = 0   =>  B = 1
340          *   tt = tc / 2 when t = TLen / 2  =>  A = 2 * (TLen - tc) / tc^2
341          *
342          * writing in terms of t
343          *   tt = (-B + (B ^ 2 + 4At) ^ 0.5) / 2A
344          * or
345          *   tt = ((1 + 4At) ^ 0.5 - 1) / 2A */
346         
347         float A = 2.0f * (TLen - tc) / (tc * tc);
348         
349         if (t > TLen / 2.0f)
350             t = tc - (sqrtf(1.0f + 4.0f * A * (TLen - t)) - 1.0f) / (2.0f * A);
351         else
352             t = (sqrtf(1.0f + 4.0f * A * t) - 1.0f) / (2.0f * A);
353         
354         TLen = tc;
355     }
356     
357     /* The cubic spline takes the form:
358      *   P(t) = p0 * a(t) + v0 * b(t) + p1 * c(t) + v1 * d(t)
359      * where p0 is the start point, v0 the start velocity, p1 the end point and
360      * v1 the end velocity.  a(t), b(t), c(t) and d(t) are cubics in t.
361      * We can show that:
362      *
363      *  a(t) = 2 * (t / TLen) ^ 3 - 3 * (t / TLen) ^ 2 + 1
364      *  b(t) = t ^ 3 / TLen ^ 2 - 2 * t ^ 2 / TLen + t
365      *  c(t) = -2 * (t / TLen) ^ 3 + 3 * (t / TLen) ^ 2
366      *  d(t) = t ^ 3 / TLen ^ 2 - t ^ 2 / TLen
367      *
368      * statisfy the boundary conditions:
369      *    P(0) = p0, P(TLen) = p1, P'(0) = v0 and P'(TLen) = v1  */
370     
371     tt = t / TLen;
372     
373     a = tt * tt * (2.0f * tt - 3.0f) + 1.0f;
374     b = t * tt * (tt - 2.0f) + t;
375     c = tt * tt * (3.0f - 2.0f * tt);
376     d = t * tt * (tt - 1.0f);
377
378     p.x = a * pP0->x + b * pV0->x + c * pP1->x + d * pV1->x;
379     p.y = a * pP0->y + b * pV0->y + c * pP1->y + d * pV1->y;
380     p.z = a * pP0->z + b * pV0->z + c * pP1->z + d * pV1->z;
381
382     p.Rot = a * NormaliseAngle(pP0->Rot) + b * pV0->Rot + 
383         c * NormaliseAngle(pP1->Rot) + d * pV1->Rot;
384     p.Elev = a * NormaliseAngle(pP0->Elev) + b * pV0->Elev + 
385         c * NormaliseAngle(pP1->Elev) + d * pV1->Elev;
386
387     return p;
388 }
389
390
391 static POS InterpolateCarry(
392     const THROW_INFO* pThrow, const THROW_INFO* pNext, float t)
393 {
394     float CT = CARRY_TIME + pThrow->TotalTime - pThrow->TimeInAir;
395     return InterpolatePosition(&pThrow->ToPos, &pThrow->ToVelocity,
396         &pNext->FromPos, &pNext->FromVelocity, CT, t);
397 }
398
399
400 /* Determine the position of the hand at a point in time. */
401
402 void GetHandPosition(
403     PATTERN_INFO* pPattern, int RightHand, float Time, POS* pPos)
404 {
405     OBJECT_POSITION* pObj = 
406         RightHand == 0 ? &pPattern->LeftHand : &pPattern->RightHand;
407     THROW_INFO* pLastThrow;
408     
409     /* Upon entry, the throw information for the relevant hand may be out of
410      * sync.  Therefore we advance through the pattern if required. */
411
412     while (pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand + pObj->TimeOffset 
413         <= (int) Time)
414     {
415         int w = pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand;
416         pObj->TimeOffset += w;
417         pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
418     }
419
420     pLastThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
421
422     /* The TimeInAir will only ever be 2 or 0 if no object is ever thrown by
423      * this hand.  In normal circumstances, 2's in the site swap are coalesced
424      * and added to TotalTime of the previous throw.  0 is a hole and means that
425      * an object isn't there.  In this case we just hold the hand still. */
426     if (pLastThrow->TimeInAir == 2 || pLastThrow->TimeInAir == 0)
427     {
428         pPos->x = pLastThrow->FromPos.x;
429         pPos->y = pLastThrow->FromPos.y;
430     }
431     else
432     {
433         /* The hand is either moving to catch the next object or carrying the
434          * next object to its next throw position.  The way THROW_INFO is
435          * structured means the relevant information for the object we're going
436          * to catch is held at the point at which it was thrown 
437          * (pNextThrownFrom).  We can't go straight for it and instead have to
438          * look at the object we've about to throw next and work out where it
439          * came from. */
440         
441         THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
442             (pObj->ThrowIndex + pLastThrow->NextForHand) % pPattern->ThrowLen];
443         
444         THROW_INFO* pNextThrownFrom = 
445             &pPattern->pThrowInfo[pNextThrow->PrevThrow];
446         
447         /* tc is a measure of how long the object we're due to catch is being
448          * carried for.  We use this to work out if we've actually caught it at
449          * this moment in time. */
450         
451         float tc = CARRY_TIME + 
452             pNextThrownFrom->TotalTime - pNextThrownFrom->TimeInAir;
453         
454         Time -= pObj->TimeOffset;
455
456         if (Time > pLastThrow->NextForHand - tc)
457         {
458             /* carrying this ball to it's new location */
459             *pPos = InterpolateCarry(pNextThrownFrom,
460                 pNextThrow, (Time - (pLastThrow->NextForHand - tc)));
461         }
462         else
463         {
464             /* going for next catch */
465             *pPos = InterpolatePosition(
466                 &pLastThrow->FromPos, &pLastThrow->FromVelocity, 
467                 &pNextThrownFrom->ToPos, &pNextThrownFrom->ToVelocity,
468                 pLastThrow->NextForHand - tc, Time);
469         }
470     }
471 }
472
473
474 static float SinDeg(float AngInDegrees)
475 {
476     return sinf(AngInDegrees * PI / 180.0f);
477 }
478
479
480 static float CosDeg(float AngInDegrees)
481 {
482     return cosf(AngInDegrees * PI / 180.0f);
483 }
484
485
486 /* Offset the specified position to get the centre of the object based on the
487  * the handle length and the current orientation */
488
489 static void OffsetHandlePosition(const POS* pPos, float HandleLen, POS* pResult)
490 {
491     pResult->x = pPos->x + HandleLen * SinDeg(pPos->Rot) * CosDeg(pPos->Elev);
492     pResult->y = pPos->y + HandleLen * SinDeg(pPos->Elev);
493     pResult->z = pPos->z + HandleLen * CosDeg(pPos->Rot) * CosDeg(pPos->Elev);
494     pResult->Elev = pPos->Elev;
495     pResult->Rot = pPos->Rot;
496 }
497
498
499 static void GetObjectPosition(
500     PATTERN_INFO* pPattern, int Obj, float Time, float HandleLen, POS* pPos)
501 {
502     OBJECT_POSITION* pObj = &pPattern->pObjectInfo[Obj];
503     THROW_INFO* pThrow;
504     
505     /* Move through the pattern, if required, such that pThrow corresponds to
506      * the current throw for this object. */
507
508     while (pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime + pObj->TimeOffset
509         <= (int) Time)
510     {
511         int w = pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime;
512         pObj->TimeOffset += w;
513         pObj->TotalTwist = NormaliseAngle(pObj->TotalTwist + 
514             GetBallTwistAmount(&pPattern->pThrowInfo[pObj->ThrowIndex]));
515         
516         pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
517     }
518
519     pThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
520
521     if (pThrow->TimeInAir == 2 || pThrow->TimeInAir == 0)
522     {
523         *pPos = pThrow->FromPos;
524         OffsetHandlePosition(pPos, HandleLen, pPos);
525     }
526     else
527     {
528         float tc = pThrow->TimeInAir - CARRY_TIME;
529         float BallTwist = GetBallTwistAmount(pThrow);
530         Time -= pObj->TimeOffset;
531         if (Time < tc)
532         {
533             /* object in air */
534             POS From, To;
535             float t, b;
536
537             t = Time / tc;
538             
539             OffsetHandlePosition(&pThrow->FromPos, HandleLen, &From);
540             OffsetHandlePosition(&pThrow->ToPos, HandleLen, &To);
541
542             b = (To.y - From.y) / tc + pPattern->Alpha * tc;
543             
544             pPos->x = (1.0f - t) * From.x + t * To.x;
545             pPos->z = (1.0f - t) * From.z + t * To.z;
546             pPos->y = -pPattern->Alpha * Time * Time + b * Time + From.y;
547             
548             if (pObj->ObjectType == OBJECT_BALL)
549                 pPos->Rot = pObj->TotalTwist + t * BallTwist;
550             else
551             {
552                 /* We describe the rotation of a club (or ring) with an
553                  * elevation and rotation but don't include a twist.
554                  * If we ignore twist for the moment, the orientation at a
555                  * rotation of r and an elevation of e can be also be expressed
556                  * by rotating the object a further 180 degrees and sort of
557                  * mirroring the rotation, e.g.:
558                  *    rot = r + 180 and elev = 180 - e
559                  * We can easily show that the maths holds, consider the
560                  * x, y ,z position of the end of a unit length club.
561                  *    y = sin(180 - e) = sin(e)
562                  *    x = cos(180 - e) * sin(r + 180) = -cos(e) * - sin(r)
563                  *    z = cos(180 - e) * cos(r + 180) = -cos(e) * - cos(r)
564                  * When a club is thrown these two potential interpretations
565                  * can produce unexpected results.
566                  * The approach we adopt is that we try and minimise the amount
567                  * of rotation we give a club -- normally this is what happens
568                  * when juggling since it's much easier to spin the club.
569                  *
570                  * When we come to drawing the object the two interpretations
571                  * aren't identical, one causes the object to twist a further
572                  * 180 about its axis.  We avoid the issue by ensuring our
573                  * objects have rotational symmetry of order 2 (e.g. we make
574                  * sure clubs have an even number of stripes) this makes the two
575                  * interpretations appear identical. */
576
577                 float RotAmt = NormaliseAngle(To.Rot - From.Rot);
578
579                 if (RotAmt < -90.0f)
580                 {
581                     To.Elev += 180  - 2 * NormaliseAngle(To.Elev);
582                     RotAmt += 180.0f;
583                 }
584                 else if (RotAmt > 90.0f)
585                 {
586                     To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
587                     RotAmt -= 180.0f;
588                 }
589
590                 pPos->Rot = From.Rot + t * RotAmt;
591             }
592
593             pPos->Elev = (1.0f - t) * From.Elev + t * To.Elev;
594
595         }
596         else
597         {
598             THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
599                    (pObj->ThrowIndex + pThrow->TotalTime) % pPattern->ThrowLen];
600
601             *pPos = InterpolateCarry(pThrow, pNextThrow, Time - tc);
602
603             if (pObj->ObjectType == OBJECT_BALL)
604                 pPos->Rot = pObj->TotalTwist + BallTwist;
605
606             OffsetHandlePosition(pPos, HandleLen, pPos);
607         }
608     }
609 }
610
611
612 /* Alpha is used to represent the acceleration due to gravity (in fact
613  * 2 * Alpha is the acceleration).  Alpha is adjusted according to the pattern
614  * being juggled.  My preference is to slow down patterns with lots of objects
615  * -- they move too fast in realtime.  Also I prefer to see a balance between
616  * the size of the figure and the height of objects thrown -- juggling patterns
617  * with large numbers of objects under real gravity can mean balls are lobbed
618  * severe heights.  Adjusting Alpha achieves both these goals.
619  *
620  * Basically we pick a height we'd like to see the biggest throw reach and then
621  * adjust Alpha to meet this. */
622
623 static void SetHeightAndAlpha(PATTERN_INFO* pPattern, 
624     const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
625 {
626     float H;
627     int MaxW = 5;
628     int i;
629     
630     if (Site != NULL)
631     {
632         for (i = 0; i < Len; i++)
633             MaxW = max(MaxW, Site[i]);
634     }
635     else
636     {
637         for (i = 0; i < Len; i++)
638             MaxW = max(MaxW, pExtInfo[i].Weight);
639     }
640     
641     /* H is the ideal max height we'd like our objects to reach.  The formula
642      * was developed by trial and error and was simply stolen from Juggle Saver.
643      * Alpha is then calculated from the classic displacement formula:
644      *   s = 0.5at^2 + ut  (where a = 2 * Alpha)
645      * We know u (the velocity) is zero at the peak, and the object should fall
646      * H units in half the time of biggest throw weight.
647      * Finally we determine the proper height the max throw reaches since this
648      * may not be H because capping may be applied (e.g. for max weights less
649      * than 5). */
650     
651     H = 8.0f * powf(MaxW / 2.0f, 0.8f) + 5.0f;
652     pPattern->Alpha = (2.0f * H) / powf(max(5, MaxW) - CARRY_TIME, 2.0f);
653     pPattern->Height = pPattern->Alpha * powf((MaxW - CARRY_TIME) * 0.5f, 2);
654 }
655
656
657 /* Where positions and spin info is not specified, generate suitable default
658  * values. */
659
660 static int GetDefaultSpins(int Weight)
661 {
662     if (Weight < 3)
663         return 0;
664     else if (Weight < 4)
665         return 1;
666     else if (Weight < 7)
667         return 2;
668     else
669         return 3;
670 }
671
672
673 static void GetDefaultFromPosition(unsigned char Side, int Weight, POS* pPos)
674 {
675     if (Weight > 4 && Weight % 2 != 0)
676         pPos->x = Side ?  -0.06f : 0.06f;
677     else if (Weight == 0 || Weight == 2)
678         pPos->x = Side ? 1.6f :  -1.6f;
679     else
680         pPos->x = Side? 0.24f :  -0.24f;
681
682     pPos->y = (Weight == 2 || Weight == 0) ? -0.25f : 0.0f;
683
684     pPos->Rot = (Weight % 2 == 0 ? -23.5f : 27.0f) * (Side ? -1.0f : 1.0f);
685
686     pPos->Elev = Weight == 1 ? -30.0f : 0.0f;
687     pPos->z = 0.0f;
688 }
689
690
691 static void GetDefaultToPosition(unsigned char Side, int Weight, POS* pPos)
692 {
693     if (Weight == 1)
694         pPos->x = Side ?  -1.0f : 1.0f;
695     else if (Weight % 2 == 0)
696         pPos->x = Side ? 2.8f :  -2.8f;
697     else
698         pPos->x = Side?  -3.1f : 3.1f;
699
700     pPos->y = -0.5f;
701
702     pPos->Rot = (Side ? -35.0f : 35.0f) * (Weight % 2 == 0 ? -1.0f : 1.0f);
703     
704     if (Weight < 2)
705         pPos->Elev = -30.0f;
706
707     else if (Weight < 4)
708         pPos->Elev = 360.0f - 50.0f;
709     else if (Weight < 7)
710         pPos->Elev = 720.0f - 50.0f;
711     else
712         pPos->Elev = 360.0f * GetDefaultSpins(Weight) - 50.0f;
713     pPos->z = 0.0f;
714 }
715
716
717 /* Update the members of PATTERN_INFO for a given juggling pattern.  The pattern
718  * can come from an ordinary siteswap (Site != NULL) or from a Juggle Saver
719  * compatible pattern that contains, position and object info etc. 
720  * We assume that patterns are valid and have at least one object (a site of
721  * zeros is invalid).  The ones we generate randomly are safe. */
722
723 static void InitPatternInfo(PATTERN_INFO* pPattern,
724     const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
725 {
726     /* Double up on the length of the site if it's of an odd length. 
727      * This way we can store position information: even indices are on one
728      * side and odds are on the other. */
729     int InfoLen = Len % 2 == 1 ? Len * 2 : Len;
730     int i;
731     THROW_INFO* pInfo = (THROW_INFO*) calloc(InfoLen, sizeof(THROW_INFO));
732     int Objects = 0;
733     unsigned char* pUsed;
734     
735     pPattern->MaxWeight = 0;
736     pPattern->ThrowLen = InfoLen;
737     pPattern->pThrowInfo = pInfo;
738     
739     SetHeightAndAlpha(pPattern, Site, pExtInfo, Len);
740
741     /* First pass through we assign the things we know about for sure just by
742      * looking at the throw weight at this position.  This includes TimeInAir;
743      * the throw and catch positions; and throw and catch velocities.
744      * Other information, like the total time for the throw (i.e. when the
745      * object is thrown again) relies on how the rest of the pattern is 
746      * structured and we defer this task for successive passes and just make
747      * guesses at this stage. */
748     
749     for (i = 0; i < InfoLen; i++)
750     {
751         float t1;
752         int w = pExtInfo != NULL ? pExtInfo[i % Len].Weight : Site[i % Len];
753
754         pInfo[i].TotalTime = pInfo[i].TimeInAir = w;
755         pInfo[(w + i) % Len].PrevThrow = i;
756
757         /* work out where we are throwing this object from and where it's going
758          * to land. */
759
760         if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_FROM_POS) == 0)
761             GetDefaultFromPosition(i % 2, w, &pInfo[i].FromPos);
762         else
763             pInfo[i].FromPos = pExtInfo[i % Len].FromPos;
764
765         if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_TO_POS) == 0)
766             GetDefaultToPosition(i % 2, w, &pInfo[i].ToPos);
767         else
768             pInfo[i].ToPos = pExtInfo[i % Len].ToPos;
769
770         /* calculate the velocity the object is moving at the start and end
771          * points -- this information is used to interpolate the hand position
772          * and to determine how the object is moved while it's carried to the 
773          * next throw position.
774          *
775          * The throw motion is governed by a parabola of the form:
776          *   y(t) = a * t ^ 2 + b * t + c
777          * Assuming at the start of the throw y(0) = y0; when it's caught
778          * y(t1) = y1; and the accelation is -2.0 * alpha the equation can be
779          * rewritten as:
780          *   y(t) = -alpha * t ^ 2 + (alpha * t1 + (y1 - y0) / t1) * t + y0
781          * making the velocity:
782          *   y'(t) = -2.0 * alpha * t + (alpha * t1 + (y1 - y0) / t1)
783          * To get the y component of velocity first we determine t1, which is
784          * the throw weight minus the time spent carrying the object.  Then
785          * perform the relevant substitutions into the above.
786          * (note: y'(t) = y'(0) - 2.0 * alpha * t)
787          * 
788          * The velocity in the x direction is constant and can be simply
789          * obtained from:
790          *   x' = (x1 - x0) / t1
791          * where x0 and x1 are the start and end x-positions respectively.
792          */
793
794         t1 = w - CARRY_TIME;
795
796         pInfo[i].FromVelocity.y = pPattern->Alpha * t1 + 
797             (pInfo[i].ToPos.y - pInfo[i].FromPos.y) / t1;
798         pInfo[i].ToVelocity.y = 
799             pInfo[i].FromVelocity.y - 2.0f * pPattern->Alpha * t1;
800         pInfo[i].FromVelocity.x = pInfo[i].ToVelocity.x = 
801             (pInfo[i].ToPos.x - pInfo[i].FromPos.x) / t1;
802         pInfo[i].FromVelocity.z = pInfo[i].ToVelocity.z = 
803             (pInfo[i].ToPos.z - pInfo[i].FromPos.z) / t1;
804         pInfo[i].FromVelocity.Rot = pInfo[i].ToVelocity.Rot =
805             (pInfo[i].ToPos.Rot - pInfo[i].FromPos.Rot) / t1;
806         pInfo[i].FromVelocity.Elev = pInfo[i].ToVelocity.Elev =
807             (pInfo[i].ToPos.Elev - pInfo[i].FromPos.Elev) / t1;
808
809
810         if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SNATCH) != 0)
811         {
812             pInfo[i].ToVelocity.x = pExtInfo[i % Len].SnatchX;
813             pInfo[i].ToVelocity.y = pExtInfo[i % Len].SnatchY;
814         }
815
816         if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SPINS) != 0)
817         {
818             pInfo[i].ToPos.Elev = 360.0f * pExtInfo[i % Len].Spins +
819                 NormaliseAngle(pInfo[i].ToPos.Elev);
820         }
821
822         Objects += w;
823         if (w > pPattern->MaxWeight)
824             pPattern->MaxWeight = w;
825     }
826
827     Objects /= InfoLen;
828
829     /* Now we go through again and work out exactly how long it is before the
830      * object is thrown again (ie. the TotalTime) typically this is the same
831      * as the time in air, however when we have a throw weight of '2' it's
832      * treated as a hold and we increase the total time accordingly. */
833
834     for (i = 0; i < InfoLen; i++)
835     {
836         if (pInfo[i].TimeInAir != 2)
837         {
838             int Next = pInfo[i].TimeInAir + i;
839             while (pInfo[Next % InfoLen].TimeInAir == 2)
840             {
841                 Next += 2;
842                 pInfo[i].TotalTime += 2;
843             }
844
845             /* patch up the Prev index.  We don't bother to see if this
846              * is different from before since it's always safe to reassign it */
847             pInfo[Next % InfoLen].PrevThrow = i;
848         }
849     }
850
851     /* then we work our way through again figuring out where the hand goes to
852      * catch something as soon as it has thrown the current object. */
853
854     for (i = 0; i < InfoLen; i++)
855     {
856         if (pInfo[i].TimeInAir != 0 && pInfo[i].TimeInAir != 2)
857         {
858             /* what we're trying to calculate is how long the hand that threw
859              * the current object has to wait before it throws another.
860              * Typically this is two beats later.  However '0' in the site swap
861              * represents a gap in a catch, and '2' represents a hold.  We skip
862              * over these until we reach the point where a ball is actually
863              * thrown. */
864             int Wait = 2;
865             while (pInfo[(i + Wait) % InfoLen].TimeInAir == 2 || 
866                 pInfo[(i + Wait) % InfoLen].TimeInAir == 0)
867             {
868                 Wait += 2;
869             }
870             pInfo[i].NextForHand = Wait;
871         }
872         else
873         {
874             /* Be careful to ensure the current weight isn't one we're trying
875              * to step over; otherwise we could potentially end up in an 
876              * infinite loop.  The value we assign may end up being used
877              * in patterns with infinite gaps (e.g. 60) or infinite holds
878              * (e.g. 62) in both cases, setting a wait of 2 ensures things
879              * are well behaved. */
880             pInfo[i].NextForHand = 2;
881         }
882     }
883
884     /* Now work out the starting positions for the objects.  To do this we
885      * unweave the initial throws so we can pick out the individual threads. */
886
887     pUsed = (unsigned char*) 
888         malloc(sizeof(unsigned char) * pPattern->MaxWeight);
889     pPattern->Objects = Objects;
890     pPattern->pObjectInfo = (OBJECT_POSITION*) calloc(
891         Objects, sizeof(OBJECT_POSITION));
892
893     for (i = 0; i < pPattern->MaxWeight; i++)
894         pUsed[i] = 0;
895
896     for (i = 0; i < pPattern->MaxWeight; i++)
897     {
898         int w = pInfo[i % InfoLen].TimeInAir;
899         if (pUsed[i] == 0 &&  w != 0)
900         {
901             Objects--;
902             pPattern->pObjectInfo[Objects].TimeOffset = i;
903             pPattern->pObjectInfo[Objects].ThrowIndex = i % InfoLen;
904             pPattern->pObjectInfo[Objects].TotalTwist = 0.0f;
905
906             if (pExtInfo != NULL && 
907                 pExtInfo[i % Len].ObjectType != OBJECT_DEFAULT)
908             {
909                 pPattern->pObjectInfo[Objects].ObjectType =
910                     pExtInfo[i % Len].ObjectType;
911             }
912             else
913             {
914                 pPattern->pObjectInfo[Objects].ObjectType = (1 + random() % 3);
915             }
916         }
917
918         if (w + i < pPattern->MaxWeight)
919             pUsed[w + i] = 1;
920         
921     }
922
923     pPattern->LeftHand.TimeOffset = pPattern->LeftHand.ThrowIndex = 0;
924     pPattern->RightHand.TimeOffset = pPattern->RightHand.ThrowIndex = 1;
925     
926     free(pUsed);
927 }
928
929
930 static void ReleasePatternInfo(PATTERN_INFO* pPattern)
931 {
932     free(pPattern->pObjectInfo);
933     free(pPattern->pThrowInfo);
934 }
935
936
937 /*****************************************************************************
938  *
939  * Sites
940  *
941  ****************************************************************************/
942
943 /* Generate a random site swap.  We assume that MaxWeight >= ObjCount and
944  * Len >= MaxWeight. */
945  
946 static int* Generate(int Len, int MaxWeight, int ObjCount)
947 {
948     int* Weight = (int*) calloc(Len, sizeof(int));
949     int* Used = (int*) calloc(Len, sizeof(int));
950     int* Options = (int*) calloc(MaxWeight + 1, sizeof(int));
951     int nOpts;
952     int i, j;
953
954     for (i = 0; i < Len; i++)
955         Weight[i] = Used[i] = -1;
956     
957     /* Pick out a unique the starting position for each object.  -2 is put in
958      * the Used array to signify this is a starting position. */
959
960     while (ObjCount > 0)
961     {
962         nOpts = 0;
963         for (j = 0; j < MaxWeight; j++)
964         {
965             if (Used[j] == -1)
966                 Options[nOpts++] = j;
967         }
968
969         Used[Options[random() % nOpts]] = -2;
970         ObjCount--;
971     }
972     
973     /* Now work our way through the pattern moving throws into an available
974      * landing positions. */
975     for (i = 0; i < Len; i++)
976     {
977         if (Used[i] == -1)
978         {
979             /* patch up holes in the pattern to zeros */
980             Used[i] = 1;
981             Weight[i] = 0;
982         }
983         else
984         {
985             /* Work out the possible places where a throw can land and pick a 
986              * weight at random. */
987             int w;
988             nOpts = 0;
989
990             for (j = 0 ; j <= MaxWeight; j++)
991             {
992                 if (Used[(i + j) % Len] == -1)
993                     Options[nOpts++] = j;
994             }
995             
996             w = Options[random() % nOpts];
997             Weight[i] = w;
998             
999             /* For starting throws make position available for a throw to land.
1000              * Because Len >= MaxWeight these positions will only be filled when
1001              * a throw wraps around the end of the site swap and therefore we
1002              * can guarantee the all the object threads will be tied up. */
1003             if (Used[i] == -2)
1004                 Used[i] = -1;
1005             
1006             Used[(i + w) % Len] = 1;
1007         }
1008     }
1009
1010     free(Options);
1011     free(Used);
1012     return Weight;
1013 }
1014
1015
1016 /* Routines to parse the Juggle Saver patterns.  These routines are a bit yucky
1017  * and make the big assumption that the patterns are well formed.  This is fine
1018  * as it stands because only 'good' ones are used but if the code is ever
1019  * extended to read arbitrary patterns (say from a file) then these routines
1020  * need to be beefed up. */
1021
1022 /* The position text looks something like (x,y,z[,rot[,elev]])
1023  * where the stuff in square brackets is optional */
1024
1025 static unsigned char ParsePositionText(const char** ppch, POS* pPos)
1026 {
1027     const char* pch = *ppch;
1028     unsigned char OK;
1029     char szTemp[32];
1030     char* pOut;
1031     float* Nums[4];
1032     int i;
1033     
1034     Nums[0] = &pPos->x;
1035     Nums[1] = &pPos->y;
1036     Nums[2] = &pPos->Rot;
1037     Nums[3] = &pPos->Elev;
1038
1039
1040     while (*pch == ' ')
1041         pch++;
1042     
1043     OK = *pch == '(';
1044     
1045     if (OK)
1046         pch++;
1047
1048     for (i = 0; OK && i < 4; i++)
1049     {
1050         pOut = szTemp;
1051         while (*pch == ' ')
1052             pch++;
1053         while (*pch != ',' && *pch != '\0' && *pch != ')' && *pch != ' ')
1054             *pOut++ = *pch++;
1055         *pOut = '\0';
1056
1057         if (szTemp[0] != '\0')
1058             *Nums[i] = (float) atof(szTemp);
1059
1060         while (*pch == ' ')
1061             pch++;
1062
1063         if (i < 3)
1064         {
1065             if (*pch == ',')
1066                 pch++;
1067             else if (*pch == ')')
1068                 break;
1069             else
1070                 OK = 0;
1071         }
1072     }
1073
1074     if (OK)
1075     {
1076         while (*pch == ' ')
1077             pch++;        
1078         if (*pch == ')')
1079             pch++;
1080         else
1081             OK = 0;
1082     }
1083
1084     *ppch = pch;
1085
1086     return OK;
1087 }
1088
1089
1090 static EXT_SITE_INFO* ParsePattern(const char* Site, int* pLen)
1091 {
1092     const char* pch = Site;
1093     int Len = 0;
1094     EXT_SITE_INFO* pInfo = NULL;
1095     unsigned char OK = 1;
1096
1097     while (OK && *pch != 0)
1098     {
1099         EXT_SITE_INFO Info;
1100         Info.Flags = 0;
1101
1102         while (*pch == ' ') pch++;
1103
1104         OK = *pch != '\0';
1105
1106         if (OK)
1107             Info.Weight = *pch >= 'A' ? *pch + 10 - 'A' : *pch - '0';
1108
1109         /* parse object type */
1110         if (OK)
1111         {
1112             pch++;
1113             while (*pch == ' ') pch++;
1114
1115             if (*pch == 'b' || *pch == 'B')
1116             {
1117                 Info.ObjectType = OBJECT_BALL;
1118                 pch++;
1119             }
1120             else if (*pch == 'c' || *pch == 'C')
1121             {
1122                 Info.ObjectType = OBJECT_CLUB;
1123                 pch++;
1124             }
1125             else if (*pch == 'r' || *pch == 'R')
1126             {
1127                 Info.ObjectType = OBJECT_RING;
1128                 pch++;
1129             }
1130             else if (*pch == 'd' || *pch == 'D')
1131             {
1132                 Info.ObjectType = OBJECT_DEFAULT;
1133                 pch++;
1134             }
1135             else
1136             {
1137                 Info.ObjectType = OBJECT_DEFAULT;
1138             }
1139         }
1140
1141         /* Parse from position */
1142         if (OK)
1143         {
1144             while (*pch == ' ') pch++;
1145             if (*pch == '@')
1146             {
1147                 pch++;
1148                 GetDefaultFromPosition(Len % 2, Info.Weight, &Info.FromPos);
1149                 Info.Flags |= HAS_FROM_POS;
1150                 OK = ParsePositionText(&pch, &Info.FromPos);
1151             }
1152         }
1153
1154         /* Parse to position */
1155         if (OK)
1156         {
1157             while (*pch == ' ') pch++;
1158             if (*pch == '>')
1159             {
1160                 pch++;
1161                 GetDefaultToPosition(Len % 2, Info.Weight, &Info.ToPos);
1162                 Info.Flags |= HAS_TO_POS;
1163                 OK = ParsePositionText(&pch, &Info.ToPos);
1164             }
1165         }
1166
1167         /* Parse snatch */
1168         if (OK)
1169         {
1170             while (*pch == ' ') pch++;
1171             if (*pch == '/')
1172             {
1173                 POS Snatch;
1174                 pch++;
1175                 Info.Flags |= HAS_SNATCH;
1176                 OK = ParsePositionText(&pch, &Snatch);
1177                 Info.SnatchX = Snatch.x;
1178                 Info.SnatchY = Snatch.y;
1179             }
1180         }
1181
1182         /* Parse Spins */
1183         if (OK)
1184         {
1185             while (*pch == ' ') pch++;
1186             if (*pch == '*')
1187             {
1188                 pch++;
1189                 OK = 0;
1190                 Info.Spins = 0;
1191                 while (*pch >= '0' && *pch <= '9')
1192                 {
1193                     OK = 1;
1194                     Info.Spins = Info.Spins * 10 + *pch - '0';
1195                     pch++;
1196                 }
1197             }
1198             else
1199                 Info.Spins = GetDefaultSpins(Info.Weight);
1200
1201             Info.Flags |= HAS_SPINS;
1202         }
1203
1204         if (OK)
1205         {
1206             if (pInfo == NULL)
1207                 pInfo = (EXT_SITE_INFO*) malloc(sizeof(EXT_SITE_INFO));
1208             else
1209                 pInfo = (EXT_SITE_INFO*) realloc(pInfo, (Len + 1) * sizeof(EXT_SITE_INFO));
1210
1211             pInfo[Len] = Info;
1212             Len++;
1213         }
1214     }
1215
1216     if (!OK && pInfo != NULL)
1217     {
1218         free(pInfo);
1219         pInfo = NULL;
1220     }
1221
1222     *pLen = Len;
1223
1224     return pInfo;
1225 }
1226
1227
1228 /*****************************************************************************
1229  *
1230  *  Juggle Saver Patterns
1231  *
1232  *****************************************************************************
1233  *
1234  * This is a selection of some of the more interesting patterns from taken
1235  * from the Juggle Saver sites.txt file.  I've only used patterns that I
1236  * originally created.
1237  */
1238
1239 const char* PatternText[] =
1240 {
1241     "9b@(-2.5,0,-70,40)>(2.5,0,70)*2 1b@(1,0,10)>(-1,0,-10)",
1242     
1243     "3B@(1,-0.4)>(2,4.2)/(-2,1)3B@(-1.8,4.4)>(-2.1,0)",
1244     
1245     "7c@(-2,0,-20)>(1.2,0,-5)7c@(2,0,20)>(-1.2,0,5)",
1246     
1247     "3b@(-0.5,0)>(1.5,0) 3b@(0.5,0)>(-1.5,0) 3r@(-2.5,3,-90,80)>(2,1,90,30)"
1248     "3b@(0.5,0)>(-1.5,0) 3b@(-0.5,0)>(1.5,0) 3r@(2.5,3,90,80)>(-2,1,-90,30)",
1249     
1250     "5c@(2,1.9,10)>(-1,1,10)5c@(2,1.8,10)>(-0.5,1.6,10)/(5,-1)"
1251     "5c@(1.6,0.2,10)>(0,-1,10)/(9,-2)5c@(-2,1.9,-10)>(1,1,-10)"
1252     "5c@(-2,1.8,-10)>(0.5,1.6,-10)/(-5,-1)5@(-1.6,0.2,-10)>(0,-1,-10)/(-9,-2)",
1253     
1254     "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)"
1255     "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)",
1256     
1257     "9c@(-2.5,0,-70,40)>(2.5,0,70)*2 1c@(1,0,10)>(-1,0,-10)*0",
1258     
1259     "3c@(2,0.5,60,0)>(1.5,4,60,80)/(-6,-12)"
1260     "3c@(-2,0.5,-60,0)>(-1.5,4,-60,80)/(6,-12)",
1261     
1262     "3c@(-0.2,0)>(1,0)3c@(0.2,0)>(-1,0)3c@(-2.5,2,-85,30)>(2.5,2,85,40)*2 "
1263     "3@(0.2,0)>(-1,0) 3@(-0.2,0)>(1,0) 3@(2.5,2,85,30)>(-2.5,2,-85,40)*2",
1264     
1265     "3c@(-0.5,-0.5,20,-30)>(2.6,4.3,60,60)/(0,1)*1 "
1266     "3c@(1.6,5.6,60,80)>(-2.6,0,-80)*0",
1267     
1268     "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)"
1269     "5c@(-0.3,0,10)>(1.2,0,10) 5c@(0.3,0,-10)>(-1.2,0,-10)"
1270     "5c@(-3,3.5,-65,80)>(3,2.5,65) 5c@(0.3,0,-10)>(-1.2,0,-10)"
1271     "5@(-0.3,0,10)>(1.2,0,10) 5@(0.3,0,-10)>(-1.2,0,-10)"
1272     "5@(-0.3,0,10)>(1.2,0,10)5@(3,3.5,65,80)>(-3,2.5,-65)"
1273 };
1274
1275
1276 /*****************************************************************************
1277  *
1278  * Rendering
1279  *
1280  *****************************************************************************/
1281
1282 static const float FOV = 70.0f;
1283 static const float BodyCol[] = {0.6f, 0.6f, 0.45f, 1.0f};
1284 static const float HandleCol[] = {0.45f, 0.45f, 0.45f, 1.0f};
1285 static const float LightPos[] = {0.0f, 200.0f, 400.0f, 1.0f};
1286 static const float LightDiff[] = {1.0f, 1.0f, 1.0f, 0.0f};
1287 static const float LightAmb[] = {0.02f, 0.02f, 0.02f, 0.0f};
1288 static const float ShoulderPos[3] = {0.95f, 2.1f, 1.7f};
1289 static const float DiffCol[] = {1.0f, 0.0f, 0.0f, 1.0f};
1290 static const float SpecCol[] = {1.0f, 1.0f, 1.0f, 1.0f};
1291
1292 static const float BallRad = 0.34f;
1293 static const float UArmLen = 1.9f;
1294 static const float LArmLen = 2.3f;
1295
1296 #define DL_BALL 0
1297 #define DL_CLUB 1
1298 #define DL_RING 2
1299 #define DL_TORSO 3
1300 #define DL_FOREARM 4
1301 #define DL_UPPERARM 5
1302
1303 static const float AltCols[][4] =
1304 {
1305     {0.0f, 0.7f, 0.0f, 1.0f},
1306     {0.0f, 0.0f, 0.9f, 1.0f},
1307     {0.0f, 0.9f, 0.9f, 1.0f},
1308     {0.45f, 0.0f, 0.9f, 1.0f},
1309     {0.9f, 0.45f, 0.0f, 1.0f},
1310     {0.0f, 0.45f, 0.9f, 1.0f},
1311     {0.9f, 0.0f, 0.9f, 1.0f},
1312     {0.9f, 0.9f, 0.0f, 1.0f},
1313     {0.9f, 0.0f, 0.45f, 1.0f},
1314     {0.45f, 0.15f, 0.6f, 1.0f}, 
1315     {0.9f, 0.0f, 0.0f, 1.0f},
1316     {0.0f, 0.9f, 0.45f, 1.0f},
1317 };
1318
1319 static const float Cols[][4] =
1320 {
1321     {0.9f, 0.0f, 0.0f, 1.0f},  /*  0 */
1322     {0.0f, 0.7f, 0.0f, 1.0f},  /*  1 */
1323     {0.0f, 0.0f, 0.9f, 1.0f},  /*  2 */
1324     {0.0f, 0.9f, 0.9f, 1.0f},  /*  3 */
1325     {0.9f, 0.0f, 0.9f, 1.0f},  /*  4 */
1326     {0.9f, 0.9f, 0.0f, 1.0f},  /*  5 */
1327     {0.9f, 0.45f, 0.0f, 1.0f}, /*  6 */
1328     {0.9f, 0.0f, 0.45f, 1.0f}, /*  7 */
1329     {0.45f, 0.9f, 0.0f, 1.0f}, /*  8 */
1330     {0.0f, 0.9f, 0.45f, 1.0f}, /*  9 */
1331     {0.45f, 0.0f, 0.9f, 1.0f}, /* 10 */
1332     {0.0f, 0.45f, 0.9f, 1.0f}, /* 11 */
1333 };
1334
1335 static int InitGLDisplayLists(void);
1336
1337
1338 void InitGLSettings(RENDER_STATE* pState, int WireFrame)
1339 {
1340     memset(pState, 0, sizeof(RENDER_STATE));
1341     
1342     pState->trackball = gltrackball_init ();
1343
1344     if (WireFrame)
1345         glPolygonMode(GL_FRONT, GL_LINE);
1346     
1347     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
1348
1349     glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
1350     glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiff);
1351     glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);
1352     
1353     glEnable(GL_SMOOTH);
1354     glEnable(GL_LIGHTING);
1355     glEnable(GL_LIGHT0);
1356
1357     glDepthFunc(GL_LESS);
1358     glEnable(GL_DEPTH_TEST);
1359
1360     glCullFace(GL_BACK);
1361     glEnable(GL_CULL_FACE);
1362     
1363     pState->DLStart = InitGLDisplayLists();
1364 }
1365
1366
1367 static void SetCamera(RENDER_STATE* pState)
1368 {
1369     /* Try to work out a sensible place to put the camera so that more or less
1370      * the whole juggling pattern fits into the screen. We assume that the
1371          * pattern is height limited (i.e. if we get the height right then the width
1372          * will be OK).  This is a pretty good assumption given that the screen
1373          * tends to wider than high, and that a juggling pattern is normally much
1374          * higher than wide.
1375      *
1376      * If I could draw a diagram here then it would be much easier to
1377      * understand but my ASCII-art skills just aren't up to it.  
1378      *
1379      * Basically we estimate a bounding volume for the juggler and objects 
1380      * throughout the pattern.  We don't fully account for the fact that the
1381      * juggler moves across the stage in an epicyclic-like motion and instead
1382      * use the near and far planes in x-y (with z = +/- w).  We also
1383      * assume that the scene is centred at x=0, this reduces our task to finding
1384      * a bounding rectangle.  Finally we need to make an estimate of the
1385      * height - for this we work out the max height of a standard throw or max
1386      * weight from the pattern; we then do a bit of adjustment to account for
1387      * a throw occurring at non-zero y values.
1388      *
1389      * Next we work out the best way to fit this rectangle into the perspective
1390      * transform.  Based on the angle of elevation (+ve angle looks down) and
1391      * the FOV we can work out whether it's the near or far corners that are
1392      * the extreme points.  And then trace back from them to find the eye
1393      * point.
1394      *
1395      */
1396      
1397     float ElevRad = pState->CameraElev * PI / 180.0f;
1398     float w = 3.0f;
1399     float cy, cz;
1400     float ey, ez;
1401     float d;
1402     float H = 0.0f;
1403     int i;
1404     float a;
1405     
1406     float tz, ty, ta;
1407     float bz, by, ba;
1408     const PATTERN_INFO* pPattern = pState->pPattern;
1409
1410     glMatrixMode(GL_PROJECTION);
1411     glLoadIdentity();
1412         
1413     for (i = 0; i < pPattern->ThrowLen; i++)
1414         H = max(H, pPattern->pThrowInfo[i].FromPos.y);
1415         
1416     H += pPattern->Height;
1417     
1418     ElevRad = pState->CameraElev * PI / 180.0f;
1419     
1420     /* ta is the angle from a point on the top of the bounding area to the eye
1421      * similarly ba is the angle from a point on the bottom. */
1422     ta = (pState->CameraElev  - (FOV - 10.0f) / 2.0f) * PI / 180.0f;
1423     ba = (pState->CameraElev  + (FOV - 10.0f) / 2.0f) * PI / 180.0f;
1424
1425     /* tz and bz hold the z location of the top and bottom extreme points.
1426      * For the top, if the angle to the eye location is positive then the
1427      * extreme point is with far z corner (the camera looks in -ve z).
1428      * The logic is reserved for the bottom. */
1429     tz = ta >= 0.0f ? -w : w;
1430     bz = ba >= 0.0f ? w : -w;
1431     
1432     ty = H;
1433     by = -1.0f;
1434     
1435     /* Solve of the eye location by using a bit of geometry.
1436      * We know the eye lies on intersection of two lines.  One comes from the
1437      * top and other from the bottom. Giving two equations:
1438      *   ez = tz + a * cos(ta) = bz + b * cos(ba)
1439      *   ey = ty + a * sin(ta) = by + b * sin(ba)
1440      * We don't bother to solve for b and use Crammer's rule to get
1441      *         | bz-tz  -cos(ba) |
1442      *         | by-ty  -sin(ba) |     
1443      *   a =  ----------------------
1444      *        | cos(ta)   -cos(ba) |
1445      *        | sin(ta)   -sin(ba) |
1446      */
1447     d = cosf(ba) * sinf(ta) - cosf(ta) * sinf(ba);
1448     a = (cosf(ba) * (by - ty) - sinf(ba) * (bz - tz)) / d;
1449     
1450     ey = ty + a * sinf(ta);
1451     ez = tz + a * cosf(ta);
1452     
1453     /* now work back from the eye point to get the lookat location */
1454     cz = 0.0;
1455     cy = ey - ez * tanf(ElevRad);
1456     
1457     /* use the distance from the eye to the scene centre to get a measure
1458      * of what the far clipping should be.  We then add on a bit more to be 
1459      * comfortable */
1460     d = sqrtf(ez * ez + (cy - ey) * (cy - ey));
1461     
1462     gluPerspective(FOV, pState->AspectRatio, 0.1f, d + 20.0f);
1463     gluLookAt(0.0, ey, ez, 0.0, cy, cz, 0.0, 1.0, 0.0);
1464
1465     glMatrixMode(GL_MODELVIEW);
1466 }
1467
1468
1469 void ResizeGL(RENDER_STATE* pState, int w, int h)
1470 {
1471     glViewport(0, 0, w, h);
1472     pState->AspectRatio = (float) w / h;
1473     SetCamera(pState);
1474 }
1475
1476
1477 /* Determine the angle at the vertex of a triangle given the length of the
1478  * three sides. */
1479
1480 static double CosineRule(double a, double b, double c)
1481 {
1482     double cosang = (a * a + b * b - c * c) / (2 * a * b);
1483     /* If lengths don't form a proper triangle return something sensible.
1484      * This typically happens with patterns where the juggler reaches too 
1485      * far to get hold of an object. */
1486     if (cosang < -1.0 || cosang > 1.0)
1487         return 0;
1488     else
1489         return 180.0 * acos(cosang) / PI;
1490 }
1491
1492
1493 /* Spheres for the balls are generated by subdividing each triangle face into
1494  * four smaller triangles.  We start with an octahedron (8 sides) and repeat the
1495  * process a number of times.  The result is a mesh that can be split into four
1496  * panels (like beanbags) and is smoother than the normal stacks and slices
1497  * approach. */
1498
1499 static void InterpolateVertex(
1500     const float* v1, const float* v2, float t, float* result)
1501 {
1502     result[0] = v1[0] * (1.0f - t) + v2[0] * t;
1503     result[1] = v1[1] * (1.0f - t) + v2[1] * t;
1504     result[2] = v1[2] * (1.0f - t) + v2[2] * t;
1505 }
1506
1507
1508 static void SetGLVertex(const float* v, float rad)
1509 {
1510     float Len = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
1511
1512     if (Len >= 1.0e-10f)
1513     {
1514         glNormal3f(v[0] / Len, v[1] / Len, v[2] / Len);
1515         glVertex3f(rad * v[0] / Len, rad * v[1] / Len, rad * v[2] / Len);
1516     }
1517     else
1518         glVertex3fv(v);
1519 }
1520
1521
1522 static void SphereSegment(
1523     const float* v1, const float* v2, const float* v3, float r, int Levels)
1524 {
1525     int i, j;
1526
1527     for (i = 0; i < Levels; i++)
1528     {
1529         float A[3], B[3], C[3], D[3];
1530         
1531         InterpolateVertex(v3, v1, (float) i / Levels, D);
1532         InterpolateVertex(v3, v1, (float)(i + 1) / Levels, A);
1533         InterpolateVertex(v3, v2, (float)(i + 1) / Levels, B);
1534         InterpolateVertex(v3, v2, (float) i / Levels, C);
1535
1536         glBegin(GL_TRIANGLE_STRIP);
1537
1538         SetGLVertex(B, r);
1539         SetGLVertex(C, r);
1540         
1541         for (j = 1; j <= i; j++)
1542         {
1543             float v[3];
1544
1545             InterpolateVertex(B, A, (float) j / (i + 1), v);
1546             SetGLVertex(v, r);
1547
1548             InterpolateVertex(C, D, (float) j / i, v);
1549             SetGLVertex(v, r);
1550         }
1551
1552         SetGLVertex(A, r);
1553         
1554         glEnd();
1555     }
1556 }
1557
1558
1559 /* OK, this function is a bit of misnomer, it only draws half a sphere.  Indeed
1560  * it draws two panels and allows us to colour this one way,  then draw the
1561  * same shape again rotated 90 degrees in a different colour.  Resulting in what
1562  * looks like a four-panel beanbag in two complementary colours. */
1563  
1564 static void DrawSphere(float rad)
1565 {
1566     int Levels = 4;
1567     float v1[3], v2[3], v3[3];
1568     
1569     v1[0] = 1.0f, v1[1] = 0.0f; v1[2] = 0.0f;
1570     v2[0] = 0.0f, v2[1] = 1.0f; v2[2] = 0.0f;
1571     v3[0] = 0.0f, v3[1] = 0.0f; v3[2] = 1.0f;
1572     SphereSegment(v1, v2, v3, rad, Levels);
1573     
1574     v2[1] = -1.0f;
1575     SphereSegment(v2, v1, v3, rad, Levels);
1576     
1577     v1[0] = v3[2] = -1.0f;
1578     SphereSegment(v2, v1, v3, rad, Levels);
1579
1580     v2[1] = 1.0f;
1581     SphereSegment(v1, v2, v3, rad, Levels);
1582 }
1583
1584
1585 static void DrawRing(void)
1586 {
1587     const int Facets = 22;
1588     const float w = 0.1f;
1589     GLUquadric* pQuad = gluNewQuadric();
1590     glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
1591     glTranslatef(0.0f, 0.0f, -w / 2.0f);
1592
1593     gluCylinder(pQuad, 1.0f, 1.0f, w, Facets, 1);
1594     gluQuadricOrientation(pQuad, GLU_INSIDE);
1595
1596     gluCylinder(pQuad, 0.7f, 0.7f, w, Facets, 1);
1597     gluQuadricOrientation(pQuad, GLU_OUTSIDE);
1598
1599     glTranslatef(0.0f, 0.0f, w);
1600     gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
1601
1602     glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
1603     glTranslatef(0.0f, 0.0f, w);
1604     gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
1605
1606     gluDeleteQuadric(pQuad);
1607 }
1608
1609
1610 /* The club follows a 'circus club' design i.e. it has stripes running down the
1611  * body.  The club is draw such that the one stripe uses the current material
1612  * and the second stripe the standard silver colour. */
1613
1614 void DrawClub(void)
1615 {
1616     const float r[4] = {0.06f, 0.1f, 0.34f, 0.34f / 2.0f};
1617     const float z[4] = {-0.4f, 0.6f, 1.35f, 2.1f};
1618     float na[4];
1619     const int n = 18;
1620     int i, j;
1621     GLUquadric* pQuad;
1622
1623     na[0] = (float) atan((r[1] - r[0]) / (z[1] - z[0]));
1624     na[1] = (float) atan((r[2] - r[1]) / (z[2] - z[1]));
1625     na[2] = (float) atan((r[3] - r[1]) / (z[3] - z[1]));
1626     na[3] = (float) atan((r[3] - r[2]) / (z[3] - z[2]));
1627
1628     for (i = 0; i < n; i += 2)
1629     {
1630         float a1 = i * PI * 2.0f / n;
1631         float a2 = (i + 1) * PI * 2.0f / n;
1632
1633         glBegin(GL_TRIANGLE_STRIP);
1634             for (j = 1; j < 4; j++)
1635             {
1636                 glNormal3f(cosf(na[j]) * cosf(a1),
1637                     cosf(na[j]) * sinf(a1), sinf(na[j]));
1638
1639                 glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1640
1641                 glNormal3f(cosf(na[j]) * cosf(a2),
1642                     cosf(na[j]) * sinf(a2),    sinf(na[j]));
1643
1644                 glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1645             }
1646         glEnd();
1647     }
1648
1649     glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, HandleCol);
1650
1651     for (i = 1; i < n; i += 2)
1652     {
1653         float a1 = i * PI * 2.0f / n;
1654         float a2 = (i + 1) * PI * 2.0f / n;
1655
1656         glBegin(GL_TRIANGLE_STRIP);
1657             for (j = 1; j < 4; j++)
1658             {
1659                 glNormal3f(cosf(na[j]) * cosf(a1),
1660                     cosf(na[j]) * sinf(a1),    sinf(na[j]));
1661
1662                 glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1663
1664                 glNormal3f(cosf(na[j]) * cosf(a2),
1665                     cosf(na[j]) * sinf(a2), sinf(na[j]));
1666
1667                 glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1668             }
1669         glEnd();
1670     }
1671
1672     pQuad = gluNewQuadric();
1673     glTranslatef(0.0f, 0.0f, z[0]);
1674     gluCylinder(pQuad, r[0], r[1], z[1] - z[0], n, 1);
1675
1676     glTranslatef(0.0f, 0.0f, z[3] - z[0]);
1677     gluDisk(pQuad, 0.0, r[3], n, 1);
1678     glRotatef(180.0f, 0.0f, 1.0f, 0.0f);
1679     glTranslatef(0.0f, 0.0f, z[3] - z[0]);
1680     gluDisk(pQuad, 0.0, r[0], n, 1);
1681     gluDeleteQuadric(pQuad);
1682 }
1683
1684
1685 /* In total 6 display lists are used.  There are created based on the DL_
1686  * constants defined earlier.  The function returns the index of the first
1687  * display list, all others can be calculated based on an offset from there. */
1688
1689 static int InitGLDisplayLists(void)
1690 {
1691     int s = glGenLists(6);
1692     GLUquadric* pQuad;
1693
1694     glNewList(s + DL_BALL, GL_COMPILE);
1695     DrawSphere(BallRad);
1696     glEndList();
1697
1698     glNewList(s + DL_CLUB, GL_COMPILE);
1699     DrawClub();
1700     glEndList();
1701
1702     glNewList(s + DL_RING, GL_COMPILE);
1703     DrawRing();
1704     glEndList();
1705     
1706     pQuad =  gluNewQuadric();
1707     gluQuadricNormals(pQuad, GLU_SMOOTH);    
1708     
1709     glNewList(s + DL_TORSO, GL_COMPILE);
1710         glPushMatrix();
1711             glTranslatef(ShoulderPos[0], ShoulderPos[1], -ShoulderPos[2]);
1712             glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
1713             gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[0] * 2, 18, 1);
1714         glPopMatrix();
1715
1716         glPushMatrix();
1717             glTranslatef(0.0f, -1.0f, -ShoulderPos[2]);
1718             glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
1719             gluCylinder(pQuad, 0.3, 0.3, ShoulderPos[1] + 1.0f, 18, 1);
1720             glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
1721             gluDisk(pQuad, 0.0, 0.3, 18, 1);
1722         glPopMatrix();
1723         
1724         /* draw the head */
1725         glPushMatrix();
1726             glTranslatef(0.0f, ShoulderPos[1] + 1.0f, -ShoulderPos[2]);
1727             glRotatef(-30.0f, 1.0f, 0.0f, 0.0f);
1728             gluCylinder(pQuad, 0.5, 0.5, 0.3, 15, 1);
1729             
1730             glPushMatrix();
1731                 glRotatef(180.0f, 1.0f, 0.0f, 0.0f);
1732                 glRotatef(180.0f, 0.0f, 0.0f, 1.0f);
1733                 gluDisk(pQuad, 0.0, 0.5, 15, 1);
1734             glPopMatrix(); 
1735                 
1736             glTranslatef(0.0f, 0.0f, .3f);
1737             gluDisk(pQuad, 0.0, 0.5, 15, 1);
1738         glPopMatrix();        
1739     glEndList();
1740     
1741     glNewList(s + DL_UPPERARM, GL_COMPILE);
1742         gluQuadricNormals(pQuad, GLU_SMOOTH);
1743         gluQuadricDrawStyle(pQuad, GLU_FILL);
1744         gluSphere(pQuad, 0.3, 12, 8);
1745
1746         gluCylinder(pQuad, 0.3, 0.3, UArmLen, 12, 1); 
1747         glTranslatef(0.0f, 0.0f, UArmLen);
1748         gluSphere(pQuad, 0.3, 12, 8);
1749     glEndList();
1750
1751     glNewList(s + DL_FOREARM, GL_COMPILE);
1752         gluCylinder(pQuad, 0.3, 0.3 / 2.0f, LArmLen, 12, 1);
1753         glTranslatef(0.0f, 0.0f, LArmLen);
1754         gluDisk(pQuad, 0, 0.3 / 2.0f, 18, 1);
1755     glEndList();
1756
1757     gluDeleteQuadric(pQuad);
1758     return s;
1759 }
1760
1761
1762 /* Drawing the arm requires connecting the upper and fore arm between the
1763  * shoulder and hand position.  Thinking about things kinematically by treating
1764  * the shoulder and elbow as ball joints then, provided the arm can stretch far
1765  * enough, there's a infnite number of ways to position the elbow.  Basically
1766  * it's possible to fix and hand and shoulder and then rotate the elbow a full
1767  * 360 degrees.  Clearly human anatomy isn't like this and picking a natural
1768  * elbow position can be complex.  We chicken out and assume that poking the
1769  * elbow out by 20 degrees from the lowest position gives a reasonably looking
1770  * orientation. */
1771
1772 void DrawArm(RENDER_STATE* pState, float TimePos, int Left)
1773 {
1774     POS Pos;
1775     float x, y, len, len2, ang, ang2;
1776     
1777     GetHandPosition(pState->pPattern, Left, TimePos, &Pos);
1778
1779     x = Pos.x + (Left ? -ShoulderPos[0] : ShoulderPos[0]);
1780     y = Pos.y - ShoulderPos[1];
1781
1782
1783     len = sqrtf(x * x + y * y + ShoulderPos[2] * ShoulderPos[2]);
1784     len2 = sqrtf(x * x + ShoulderPos[2] * ShoulderPos[2]);
1785
1786     ang = (float) CosineRule(UArmLen, len, LArmLen);
1787     ang2 = (float) CosineRule(UArmLen, LArmLen, len);
1788
1789     if (ang == 0.0 && ang2 == 0)
1790         ang2 = 180.0;
1791
1792
1793     glPushMatrix();
1794         glTranslatef(Left ? ShoulderPos[0] : -ShoulderPos[0], ShoulderPos[1],
1795             -ShoulderPos[2]);
1796         glRotatef((float)(180.0f * asin(x / len2) / 3.14f), 0.0f, 1.0f, 0.0f);
1797         glRotatef((float)(-180.f * asin(y / len) / 3.14), 1.0f, 0.0f, 0.0f);
1798         glRotatef(Left ? 20.0f : -20.0f, 0.0f, 0.0f, 1.0f);
1799         glRotatef((float) ang, 1.0f, 0.0f, 0.0f);
1800         glCallList(DL_UPPERARM + pState->DLStart);
1801
1802         glRotatef((float)(ang2 - 180.0), 1.0f, 0.0f, 0.f);
1803         glCallList(DL_FOREARM + pState->DLStart);
1804     glPopMatrix();
1805 }
1806
1807
1808 void DrawGLScene(RENDER_STATE* pState)
1809 {
1810     float Time = pState->Time;
1811     int nCols = sizeof(Cols) / sizeof(Cols[0]);
1812     int i;
1813
1814     PATTERN_INFO* pPattern = pState->pPattern;
1815
1816     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
1817
1818     glMatrixMode(GL_MODELVIEW);
1819     glLoadIdentity();
1820     glTranslatef(5.0f * sinf(pState->TranslateAngle), 0.0f, 0.0f);
1821
1822     gltrackball_rotate (pState->trackball);
1823
1824     glRotatef(pState->SpinAngle, 0.0f, 1.0f, 0.0f);
1825     glTranslatef(0.0, 0.0, -1.0f);
1826
1827     glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, DiffCol);
1828     glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, SpecCol);
1829     glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 60.0f);
1830
1831     for (i = 0; i < pPattern->Objects; i++)
1832     {
1833         POS ObjPos;
1834         
1835         glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Cols[i % nCols]);
1836         glPushMatrix();
1837
1838         switch (pPattern->pObjectInfo[i].ObjectType)
1839         {
1840             case OBJECT_CLUB:
1841                 GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos);
1842                 glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);
1843                 glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f);
1844                 glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f);
1845                 glTranslatef(0.0f, 0.0f, -1.0f);
1846                 glCallList(DL_CLUB + pState->DLStart);
1847                 break;
1848
1849             case OBJECT_RING:
1850                 GetObjectPosition(pPattern, i, Time, 1.0f, &ObjPos);
1851                 glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);
1852                 glRotatef(ObjPos.Rot, 0.0f, 1.0f, 0.0f);
1853                 glRotatef(ObjPos.Elev, -1.0f, 0.0f, 0.0f);
1854                 glCallList(DL_RING + pState->DLStart);
1855                 break;
1856
1857             default:
1858                 GetObjectPosition(pPattern, i, Time, 0.0f, &ObjPos);
1859                 glTranslatef(ObjPos.x, ObjPos.y, ObjPos.z);        
1860                 glRotatef(ObjPos.Rot, 0.6963f, 0.6963f, 0.1742f);
1861                 glCallList(DL_BALL + pState->DLStart);
1862                 glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
1863                 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 
1864                     AltCols[i % nCols]);
1865                 glCallList(DL_BALL + pState->DLStart);
1866                 break;
1867         }
1868
1869         glPopMatrix();
1870     }
1871
1872     glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, BodyCol);
1873     glCallList(DL_TORSO + pState->DLStart);
1874     DrawArm(pState, Time, 1);
1875     DrawArm(pState, Time, 0);
1876 }
1877
1878
1879 static int RandInRange(int Min, int Max)
1880 {
1881     return Min + random() % (1 + Max - Min);
1882 }
1883
1884
1885 extern void UpdatePattern(
1886     RENDER_STATE* pState, int MinBalls, int MaxBalls, 
1887     int MinHeightInc, int MaxHeightInc)
1888 {
1889     if (pState->pPattern != NULL)
1890         ReleasePatternInfo(pState->pPattern);
1891     
1892     pState->pPattern = (PATTERN_INFO*) malloc(sizeof(PATTERN_INFO));
1893     
1894     if ((random() % 3) == 1)
1895     {    
1896         int ExtSiteLen;
1897         int n = random() % (sizeof(PatternText) / sizeof(PatternText[0]));
1898         EXT_SITE_INFO* pExtInfo = ParsePattern(PatternText[n], &ExtSiteLen);
1899         InitPatternInfo(pState->pPattern, NULL, pExtInfo, ExtSiteLen);
1900         free(pExtInfo);
1901     }
1902     else
1903     {
1904         int* pRand;
1905         int ballcount, maxweight;
1906         const int RandPatternLen = 1500;
1907         
1908         ballcount = RandInRange(MinBalls, MaxBalls);
1909         maxweight = ballcount  + RandInRange(MinHeightInc, MaxHeightInc);
1910         
1911         pRand = Generate(RandPatternLen, maxweight, ballcount);
1912         InitPatternInfo(pState->pPattern, pRand, NULL, RandPatternLen);
1913         free(pRand);
1914     }
1915     
1916     pState->CameraElev = 50.0f - random() % 90;
1917     pState->TranslateAngle = random() % 360;
1918     pState->SpinAngle = random() % 360;
1919     pState->Time = 50.0f;
1920     SetCamera(pState);
1921 }
1922
1923
1924 /*******************************************************************************
1925  *
1926  *  XScreenSaver Configuration
1927  *
1928  ******************************************************************************/
1929
1930 extern XtAppContext app;
1931
1932 typedef struct
1933 {
1934     GLXContext* glxContext;
1935     RENDER_STATE RenderState;
1936     float CurrentFrameRate;
1937     unsigned FramesSinceSync;
1938     unsigned LastSyncTime;
1939 } JUGGLER3D_CONFIG;
1940
1941
1942 static JUGGLER3D_CONFIG* pConfigInfo = NULL;
1943 static int MaxObjects;
1944 static int MinObjects;
1945 static int MaxHeightInc;
1946 static int MinHeightInc;
1947 static float SpinSpeed;
1948 static float TranslateSpeed;
1949 static float JuggleSpeed;
1950
1951 static XrmOptionDescRec Options[] =
1952 {
1953     {"-spin", ".spin", XrmoptionSepArg, 0},
1954     {"-trans", ".trans", XrmoptionSepArg, 0},
1955     {"-speed", ".speed", XrmoptionSepArg, 0},
1956     {"-maxobjs", ".maxobjs", XrmoptionSepArg, 0},
1957     {"-minobjs", ".minobjs", XrmoptionSepArg, 0},
1958     {"-maxhinc", ".maxhinc", XrmoptionSepArg, 0},
1959     {"-minhinc", ".minhinc", XrmoptionSepArg, 0},
1960 };
1961
1962
1963 static argtype Vars[] = 
1964 {
1965     {&MaxObjects, "maxobjs", "MaxObjs", "8", t_Int},
1966     {&MinObjects, "minobjs", "MinObjs", "3", t_Int},
1967     {&MaxHeightInc, "maxhinc", "MaxHInc", "6", t_Int},
1968     {&MinHeightInc, "minhinc", "MaxHInc", "2", t_Int},
1969     {&JuggleSpeed, "speed", "JuggleSpeed", "2.2", t_Float},
1970     {&TranslateSpeed, "trans", "TranslateSpeed", "0.1", t_Float},
1971     {&SpinSpeed, "spin", "SpinSpeed", "20.0", t_Float},
1972 };
1973
1974
1975 ModeSpecOpt SWITCH_OPTS = {countof(Options), Options, countof(Vars), Vars};
1976
1977
1978 void Juggler3D_HackReshapeEvent(ModeInfo *mi, int width, int height)
1979 {
1980     JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
1981     ResizeGL(&pConfig->RenderState, width, height);
1982 }
1983
1984
1985 void Juggler3D_HackInitEvent(ModeInfo* mi)
1986 {
1987     JUGGLER3D_CONFIG* pConfig;
1988     
1989     if (pConfigInfo == NULL)
1990     {
1991         /* Apply suitable bounds checks to the input parameters */
1992         MaxObjects = max(3, min(MaxObjects, 36));
1993         MinObjects = max(3, min(MinObjects, MaxObjects));
1994
1995         MaxHeightInc = max(1, min(MaxHeightInc, 32));
1996         MinHeightInc = max(1, min(MinHeightInc, MaxHeightInc));
1997             
1998         pConfigInfo = (JUGGLER3D_CONFIG*) calloc(
1999             MI_NUM_SCREENS(mi), sizeof(JUGGLER3D_CONFIG));
2000         if (pConfigInfo == NULL)
2001         {
2002             fprintf(stderr, "%s: out of memory\n", progname);
2003             exit(1);
2004         }
2005     }
2006     
2007     pConfig = &pConfigInfo[MI_SCREEN(mi)];
2008     pConfig->glxContext = init_GL(mi);
2009     pConfig->CurrentFrameRate = 0.0f;
2010     pConfig->FramesSinceSync = 0;
2011     pConfig->LastSyncTime = 0;
2012     InitGLSettings(&pConfig->RenderState, MI_IS_WIREFRAME(mi));
2013
2014     UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
2015         MinHeightInc, MaxHeightInc);
2016     
2017     Juggler3D_HackReshapeEvent(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2018 }
2019
2020
2021 void Juggler3D_HackDrawEvent(ModeInfo* mi)
2022 {
2023     JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2024     Display* pDisplay = MI_DISPLAY(mi);
2025     Window hwnd = MI_WINDOW(mi);
2026
2027     if (pConfig->glxContext == NULL)
2028         return;
2029     
2030     /* While drawing, keep track of the rendering speed so we can adjust the
2031      * animation speed so things appear consistent.  The basis of the this
2032      * code comes from the frame rate counter (fps.c) but has been modified
2033      * so that it reports the initial frame rate earlier (after 0.02 secs
2034      * instead of 1 sec). */
2035     
2036     if (pConfig->FramesSinceSync >=  1 * (int) pConfig->CurrentFrameRate)
2037     {
2038         struct timeval tvnow;
2039         unsigned now;
2040             
2041         # ifdef GETTIMEOFDAY_TWO_ARGS
2042             struct timezone tzp;
2043             gettimeofday(&tvnow, &tzp);
2044         # else
2045             gettimeofday(&tvnow);
2046         # endif
2047         
2048         now = (unsigned) (tvnow.tv_sec * 1000000 + tvnow.tv_usec);
2049         if (pConfig->FramesSinceSync == 0)
2050         {
2051             pConfig->LastSyncTime = now;
2052         }
2053         else
2054         {
2055             unsigned Delta = now - pConfig->LastSyncTime;
2056             if (Delta > 20000)
2057             {
2058                 pConfig->LastSyncTime = now;
2059                 pConfig->CurrentFrameRate = 
2060                     (pConfig->FramesSinceSync * 1.0e6f) / Delta;
2061                 pConfig->FramesSinceSync = 0;
2062             }
2063         }
2064     }
2065     
2066     pConfig->FramesSinceSync++;
2067     
2068     if (pConfig->RenderState.Time > 150.0f)
2069     {
2070         UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
2071             MinHeightInc, MaxHeightInc);
2072     }
2073     DrawGLScene(&pConfig->RenderState);
2074     
2075     if (pConfig->CurrentFrameRate > 1.0e-6f)
2076     {
2077         pConfig->RenderState.Time += JuggleSpeed / pConfig->CurrentFrameRate;
2078         pConfig->RenderState.SpinAngle += SpinSpeed / pConfig->CurrentFrameRate;
2079         pConfig->RenderState.TranslateAngle += 
2080             TranslateSpeed / pConfig->CurrentFrameRate;
2081     }
2082     
2083     if (mi->fps_p)
2084         do_fps(mi);
2085   
2086     glFinish();
2087     glXSwapBuffers(pDisplay, hwnd);
2088 }
2089
2090
2091 Bool Juggler3D_HackHandleEvent(ModeInfo* mi, XEvent* pEvent)
2092 {
2093   JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2094   RENDER_STATE* pState = &pConfig->RenderState;
2095
2096     if (pEvent->xany.type == ButtonPress &&
2097         pEvent->xbutton.button == Button1)
2098     {
2099       pState->button_down_p = True;
2100       gltrackball_start (pState->trackball,
2101                          pEvent->xbutton.x, pEvent->xbutton.y,
2102                          MI_WIDTH (mi), MI_HEIGHT (mi));
2103       return True;
2104     }
2105     else if (pEvent->xany.type == ButtonRelease &&
2106              pEvent->xbutton.button == Button1)
2107     {
2108       pState->button_down_p = False;
2109       return True;
2110     }
2111     else if (pEvent->xany.type == ButtonPress &&
2112              (pEvent->xbutton.button == Button4 ||
2113               pEvent->xbutton.button == Button5))
2114     {
2115       gltrackball_mousewheel (pState->trackball, pEvent->xbutton.button, 2,
2116                               !pEvent->xbutton.state);
2117       return True;
2118     }
2119     else if (pEvent->xany.type == MotionNotify &&
2120              pState->button_down_p)
2121     {
2122       gltrackball_track (pState->trackball,
2123                          pEvent->xmotion.x, pEvent->xmotion.y,
2124                          MI_WIDTH (mi), MI_HEIGHT (mi));
2125       return True;
2126     }
2127     else if (pEvent->xany.type == KeyPress)
2128     {
2129         char str[20];
2130         KeySym Key = 0;
2131         int count = XLookupString(&pEvent->xkey, str, 20, &Key, 0);
2132         str[count] = '\0';
2133         if (*str == ' ')
2134         {
2135             UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects, 
2136                 MinHeightInc, MaxHeightInc);
2137         }
2138     }
2139     
2140     return False;
2141 }
2142
2143 #endif