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