1 /* Juggler3D, Copyright (c) 2005 Brian Apps <brian@jugglesaver.co.uk>
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. */
10 #include <X11/Intrinsic.h>
13 #define countof(x) (sizeof((x))/sizeof((*x)))
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
24 "*JuggleSpeed: 0.15\n*delay: 20000\n*showFPS: False\n*wireframe: False\n"
26 #include "xlockmore.h"
27 #include "gltrackball.h"
29 #ifdef USE_GL /* whole file */
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) */
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)))
47 #define max(a, b) ((a) > (b) ? (a) : (b))
48 #define min(a, b) ((a) < (b) ? (a) : (b))
51 /******************************************************************************
53 * The code is broadly split into the following parts:
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.
65 *****************************************************************************/
68 /*****************************************************************************
72 *****************************************************************************/
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.
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. */
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
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
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. */
130 /* OBJECT_POSITION works with the array of THROW_INFOs to allow us to determine
131 * exactly where an object or hand is.
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
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. */
141 #define OBJECT_DEFAULT 0
142 #define OBJECT_BALL 1
143 #define OBJECT_CLUB 2
144 #define OBJECT_RING 3
155 /* PATTERN_INFO is the main structure that holds the information about a
158 * pThrowInfo is an array of ThrowLen elements that describes each throw in the
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
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. */
170 THROW_INFO* pThrowInfo;
173 OBJECT_POSITION* pObjectInfo;
176 OBJECT_POSITION LeftHand;
177 OBJECT_POSITION RightHand;
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. */
190 #define HAS_FROM_POS 1
208 /* RENDER_STATE is used to co-ordinate the OpenGL rendering of the juggler and
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
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.
224 PATTERN_INFO* pPattern;
230 float TranslateAngle;
233 trackball_state *trackball;
239 /*****************************************************************************
243 ****************************************************************************
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.
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).
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.)
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.
269 const float CARRY_TIME = 0.56f;
270 const float PI = 3.14159265358979f;
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. */
276 static float GetBallTwistAmount(const THROW_INFO* pThrow)
278 if (pThrow->FromPos.x > pThrow->ToPos.x)
279 return 18.0f * powf(pThrow->TimeInAir, 1.5);
281 return -18.0f * powf(pThrow->TimeInAir, 1.5);
285 float NormaliseAngle(float Ang)
289 int i = (int) (Ang + 180.0f) / 360;
290 return Ang - 360.0f * i;
294 int i = (int)(180.0f - Ang) / 360;
295 return Ang + i * 360.0f;
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:
309 static POS InterpolatePosition(
310 const POS* pP0, const POS* pV0, const POS* pP1, const POS* pV1,
314 float a, b, c, d, tt, tc;
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
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.
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.
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
342 * writing in terms of t
343 * tt = (-B + (B ^ 2 + 4At) ^ 0.5) / 2A
345 * tt = ((1 + 4At) ^ 0.5 - 1) / 2A */
347 float A = 2.0f * (TLen - tc) / (tc * tc);
350 t = tc - (sqrtf(1.0f + 4.0f * A * (TLen - t)) - 1.0f) / (2.0f * A);
352 t = (sqrtf(1.0f + 4.0f * A * t) - 1.0f) / (2.0f * A);
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.
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
368 * statisfy the boundary conditions:
369 * P(0) = p0, P(TLen) = p1, P'(0) = v0 and P'(TLen) = v1 */
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);
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;
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;
391 static POS InterpolateCarry(
392 const THROW_INFO* pThrow, const THROW_INFO* pNext, float t)
394 float CT = CARRY_TIME + pThrow->TotalTime - pThrow->TimeInAir;
395 return InterpolatePosition(&pThrow->ToPos, &pThrow->ToVelocity,
396 &pNext->FromPos, &pNext->FromVelocity, CT, t);
400 /* Determine the position of the hand at a point in time. */
402 void GetHandPosition(
403 PATTERN_INFO* pPattern, int RightHand, float Time, POS* pPos)
405 OBJECT_POSITION* pObj =
406 RightHand == 0 ? &pPattern->LeftHand : &pPattern->RightHand;
407 THROW_INFO* pLastThrow;
409 /* Upon entry, the throw information for the relevant hand may be out of
410 * sync. Therefore we advance through the pattern if required. */
412 while (pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand + pObj->TimeOffset
415 int w = pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand;
416 pObj->TimeOffset += w;
417 pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
420 pLastThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
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)
428 pPos->x = pLastThrow->FromPos.x;
429 pPos->y = pLastThrow->FromPos.y;
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
441 THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
442 (pObj->ThrowIndex + pLastThrow->NextForHand) % pPattern->ThrowLen];
444 THROW_INFO* pNextThrownFrom =
445 &pPattern->pThrowInfo[pNextThrow->PrevThrow];
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. */
451 float tc = CARRY_TIME +
452 pNextThrownFrom->TotalTime - pNextThrownFrom->TimeInAir;
454 Time -= pObj->TimeOffset;
456 if (Time > pLastThrow->NextForHand - tc)
458 /* carrying this ball to it's new location */
459 *pPos = InterpolateCarry(pNextThrownFrom,
460 pNextThrow, (Time - (pLastThrow->NextForHand - tc)));
464 /* going for next catch */
465 *pPos = InterpolatePosition(
466 &pLastThrow->FromPos, &pLastThrow->FromVelocity,
467 &pNextThrownFrom->ToPos, &pNextThrownFrom->ToVelocity,
468 pLastThrow->NextForHand - tc, Time);
474 static float SinDeg(float AngInDegrees)
476 return sinf(AngInDegrees * PI / 180.0f);
480 static float CosDeg(float AngInDegrees)
482 return cosf(AngInDegrees * PI / 180.0f);
486 /* Offset the specified position to get the centre of the object based on the
487 * the handle length and the current orientation */
489 static void OffsetHandlePosition(const POS* pPos, float HandleLen, POS* pResult)
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;
499 static void GetObjectPosition(
500 PATTERN_INFO* pPattern, int Obj, float Time, float HandleLen, POS* pPos)
502 OBJECT_POSITION* pObj = &pPattern->pObjectInfo[Obj];
505 /* Move through the pattern, if required, such that pThrow corresponds to
506 * the current throw for this object. */
508 while (pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime + pObj->TimeOffset
511 int w = pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime;
512 pObj->TimeOffset += w;
513 pObj->TotalTwist = NormaliseAngle(pObj->TotalTwist +
514 GetBallTwistAmount(&pPattern->pThrowInfo[pObj->ThrowIndex]));
516 pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
519 pThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
521 if (pThrow->TimeInAir == 2 || pThrow->TimeInAir == 0)
523 *pPos = pThrow->FromPos;
524 OffsetHandlePosition(pPos, HandleLen, pPos);
528 float tc = pThrow->TimeInAir - CARRY_TIME;
529 float BallTwist = GetBallTwistAmount(pThrow);
530 Time -= pObj->TimeOffset;
539 OffsetHandlePosition(&pThrow->FromPos, HandleLen, &From);
540 OffsetHandlePosition(&pThrow->ToPos, HandleLen, &To);
542 b = (To.y - From.y) / tc + pPattern->Alpha * tc;
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;
548 if (pObj->ObjectType == OBJECT_BALL)
549 pPos->Rot = pObj->TotalTwist + t * BallTwist;
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.
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. */
577 float RotAmt = NormaliseAngle(To.Rot - From.Rot);
581 To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
584 else if (RotAmt > 90.0f)
586 To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
590 pPos->Rot = From.Rot + t * RotAmt;
593 pPos->Elev = (1.0f - t) * From.Elev + t * To.Elev;
598 THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
599 (pObj->ThrowIndex + pThrow->TotalTime) % pPattern->ThrowLen];
601 *pPos = InterpolateCarry(pThrow, pNextThrow, Time - tc);
603 if (pObj->ObjectType == OBJECT_BALL)
604 pPos->Rot = pObj->TotalTwist + BallTwist;
606 OffsetHandlePosition(pPos, HandleLen, pPos);
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.
620 * Basically we pick a height we'd like to see the biggest throw reach and then
621 * adjust Alpha to meet this. */
623 static void SetHeightAndAlpha(PATTERN_INFO* pPattern,
624 const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
632 for (i = 0; i < Len; i++)
633 MaxW = max(MaxW, Site[i]);
637 for (i = 0; i < Len; i++)
638 MaxW = max(MaxW, pExtInfo[i].Weight);
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
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);
657 /* Where positions and spin info is not specified, generate suitable default
660 static int GetDefaultSpins(int Weight)
673 static void GetDefaultFromPosition(unsigned char Side, int Weight, POS* pPos)
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;
680 pPos->x = Side? 0.24f : -0.24f;
682 pPos->y = (Weight == 2 || Weight == 0) ? -0.25f : 0.0f;
684 pPos->Rot = (Weight % 2 == 0 ? -23.5f : 27.0f) * (Side ? -1.0f : 1.0f);
686 pPos->Elev = Weight == 1 ? -30.0f : 0.0f;
691 static void GetDefaultToPosition(unsigned char Side, int Weight, POS* pPos)
694 pPos->x = Side ? -1.0f : 1.0f;
695 else if (Weight % 2 == 0)
696 pPos->x = Side ? 2.8f : -2.8f;
698 pPos->x = Side? -3.1f : 3.1f;
702 pPos->Rot = (Side ? -35.0f : 35.0f) * (Weight % 2 == 0 ? -1.0f : 1.0f);
708 pPos->Elev = 360.0f - 50.0f;
710 pPos->Elev = 720.0f - 50.0f;
712 pPos->Elev = 360.0f * GetDefaultSpins(Weight) - 50.0f;
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. */
723 static void InitPatternInfo(PATTERN_INFO* pPattern,
724 const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
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;
731 THROW_INFO* pInfo = (THROW_INFO*) calloc(InfoLen, sizeof(THROW_INFO));
733 unsigned char* pUsed;
735 pPattern->MaxWeight = 0;
736 pPattern->ThrowLen = InfoLen;
737 pPattern->pThrowInfo = pInfo;
739 SetHeightAndAlpha(pPattern, Site, pExtInfo, Len);
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. */
749 for (i = 0; i < InfoLen; i++)
752 int w = pExtInfo != NULL ? pExtInfo[i % Len].Weight : Site[i % Len];
754 pInfo[i].TotalTime = pInfo[i].TimeInAir = w;
755 pInfo[(w + i) % Len].PrevThrow = i;
757 /* work out where we are throwing this object from and where it's going
760 if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_FROM_POS) == 0)
761 GetDefaultFromPosition(i % 2, w, &pInfo[i].FromPos);
763 pInfo[i].FromPos = pExtInfo[i % Len].FromPos;
765 if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_TO_POS) == 0)
766 GetDefaultToPosition(i % 2, w, &pInfo[i].ToPos);
768 pInfo[i].ToPos = pExtInfo[i % Len].ToPos;
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.
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
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)
788 * The velocity in the x direction is constant and can be simply
790 * x' = (x1 - x0) / t1
791 * where x0 and x1 are the start and end x-positions respectively.
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;
810 if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SNATCH) != 0)
812 pInfo[i].ToVelocity.x = pExtInfo[i % Len].SnatchX;
813 pInfo[i].ToVelocity.y = pExtInfo[i % Len].SnatchY;
816 if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SPINS) != 0)
818 pInfo[i].ToPos.Elev = 360.0f * pExtInfo[i % Len].Spins +
819 NormaliseAngle(pInfo[i].ToPos.Elev);
823 if (w > pPattern->MaxWeight)
824 pPattern->MaxWeight = w;
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. */
834 for (i = 0; i < InfoLen; i++)
836 if (pInfo[i].TimeInAir != 2)
838 int Next = pInfo[i].TimeInAir + i;
839 while (pInfo[Next % InfoLen].TimeInAir == 2)
842 pInfo[i].TotalTime += 2;
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;
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. */
854 for (i = 0; i < InfoLen; i++)
856 if (pInfo[i].TimeInAir != 0 && pInfo[i].TimeInAir != 2)
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
865 while (pInfo[(i + Wait) % InfoLen].TimeInAir == 2 ||
866 pInfo[(i + Wait) % InfoLen].TimeInAir == 0)
870 pInfo[i].NextForHand = Wait;
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;
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. */
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));
893 for (i = 0; i < pPattern->MaxWeight; i++)
896 for (i = 0; i < pPattern->MaxWeight; i++)
898 int w = pInfo[i % InfoLen].TimeInAir;
899 if (pUsed[i] == 0 && w != 0)
902 pPattern->pObjectInfo[Objects].TimeOffset = i;
903 pPattern->pObjectInfo[Objects].ThrowIndex = i % InfoLen;
904 pPattern->pObjectInfo[Objects].TotalTwist = 0.0f;
906 if (pExtInfo != NULL &&
907 pExtInfo[i % Len].ObjectType != OBJECT_DEFAULT)
909 pPattern->pObjectInfo[Objects].ObjectType =
910 pExtInfo[i % Len].ObjectType;
914 pPattern->pObjectInfo[Objects].ObjectType = (1 + random() % 3);
918 if (w + i < pPattern->MaxWeight)
923 pPattern->LeftHand.TimeOffset = pPattern->LeftHand.ThrowIndex = 0;
924 pPattern->RightHand.TimeOffset = pPattern->RightHand.ThrowIndex = 1;
930 static void ReleasePatternInfo(PATTERN_INFO* pPattern)
932 free(pPattern->pObjectInfo);
933 free(pPattern->pThrowInfo);
937 /*****************************************************************************
941 ****************************************************************************/
943 /* Generate a random site swap. We assume that MaxWeight >= ObjCount and
944 * Len >= MaxWeight. */
946 static int* Generate(int Len, int MaxWeight, int ObjCount)
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));
954 for (i = 0; i < Len; i++)
955 Weight[i] = Used[i] = -1;
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. */
963 for (j = 0; j < MaxWeight; j++)
966 Options[nOpts++] = j;
969 Used[Options[random() % nOpts]] = -2;
973 /* Now work our way through the pattern moving throws into an available
974 * landing positions. */
975 for (i = 0; i < Len; i++)
979 /* patch up holes in the pattern to zeros */
985 /* Work out the possible places where a throw can land and pick a
986 * weight at random. */
990 for (j = 0 ; j <= MaxWeight; j++)
992 if (Used[(i + j) % Len] == -1)
993 Options[nOpts++] = j;
996 w = Options[random() % nOpts];
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. */
1006 Used[(i + w) % Len] = 1;
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. */
1022 /* The position text looks something like (x,y,z[,rot[,elev]])
1023 * where the stuff in square brackets is optional */
1025 static unsigned char ParsePositionText(const char** ppch, POS* pPos)
1027 const char* pch = *ppch;
1036 Nums[2] = &pPos->Rot;
1037 Nums[3] = &pPos->Elev;
1048 for (i = 0; OK && i < 4; i++)
1053 while (*pch != ',' && *pch != '\0' && *pch != ')' && *pch != ' ')
1057 if (szTemp[0] != '\0')
1058 *Nums[i] = (float) atof(szTemp);
1067 else if (*pch == ')')
1090 static EXT_SITE_INFO* ParsePattern(const char* Site, int* pLen)
1092 const char* pch = Site;
1094 EXT_SITE_INFO* pInfo = NULL;
1095 unsigned char OK = 1;
1097 while (OK && *pch != 0)
1102 while (*pch == ' ') pch++;
1107 Info.Weight = *pch >= 'A' ? *pch + 10 - 'A' : *pch - '0';
1109 /* parse object type */
1113 while (*pch == ' ') pch++;
1115 if (*pch == 'b' || *pch == 'B')
1117 Info.ObjectType = OBJECT_BALL;
1120 else if (*pch == 'c' || *pch == 'C')
1122 Info.ObjectType = OBJECT_CLUB;
1125 else if (*pch == 'r' || *pch == 'R')
1127 Info.ObjectType = OBJECT_RING;
1130 else if (*pch == 'd' || *pch == 'D')
1132 Info.ObjectType = OBJECT_DEFAULT;
1137 Info.ObjectType = OBJECT_DEFAULT;
1141 /* Parse from position */
1144 while (*pch == ' ') pch++;
1148 GetDefaultFromPosition(Len % 2, Info.Weight, &Info.FromPos);
1149 Info.Flags |= HAS_FROM_POS;
1150 OK = ParsePositionText(&pch, &Info.FromPos);
1154 /* Parse to position */
1157 while (*pch == ' ') pch++;
1161 GetDefaultToPosition(Len % 2, Info.Weight, &Info.ToPos);
1162 Info.Flags |= HAS_TO_POS;
1163 OK = ParsePositionText(&pch, &Info.ToPos);
1170 while (*pch == ' ') pch++;
1175 Info.Flags |= HAS_SNATCH;
1176 OK = ParsePositionText(&pch, &Snatch);
1177 Info.SnatchX = Snatch.x;
1178 Info.SnatchY = Snatch.y;
1185 while (*pch == ' ') pch++;
1191 while (*pch >= '0' && *pch <= '9')
1194 Info.Spins = Info.Spins * 10 + *pch - '0';
1199 Info.Spins = GetDefaultSpins(Info.Weight);
1201 Info.Flags |= HAS_SPINS;
1207 pInfo = (EXT_SITE_INFO*) malloc(sizeof(EXT_SITE_INFO));
1209 pInfo = (EXT_SITE_INFO*) realloc(pInfo, (Len + 1) * sizeof(EXT_SITE_INFO));
1216 if (!OK && pInfo != NULL)
1228 /*****************************************************************************
1230 * Juggle Saver Patterns
1232 *****************************************************************************
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.
1239 const char* PatternText[] =
1241 "9b@(-2.5,0,-70,40)>(2.5,0,70)*2 1b@(1,0,10)>(-1,0,-10)",
1243 "3B@(1,-0.4)>(2,4.2)/(-2,1)3B@(-1.8,4.4)>(-2.1,0)",
1245 "7c@(-2,0,-20)>(1.2,0,-5)7c@(2,0,20)>(-1.2,0,5)",
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)",
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)",
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)",
1257 "9c@(-2.5,0,-70,40)>(2.5,0,70)*2 1c@(1,0,10)>(-1,0,-10)*0",
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)",
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",
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",
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)"
1276 /*****************************************************************************
1280 *****************************************************************************/
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};
1292 static const float BallRad = 0.34f;
1293 static const float UArmLen = 1.9f;
1294 static const float LArmLen = 2.3f;
1300 #define DL_FOREARM 4
1301 #define DL_UPPERARM 5
1303 static const float AltCols[][4] =
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},
1319 static const float Cols[][4] =
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 */
1335 static int InitGLDisplayLists(void);
1338 void InitGLSettings(RENDER_STATE* pState, int WireFrame)
1340 memset(pState, 0, sizeof(RENDER_STATE));
1342 pState->trackball = gltrackball_init ();
1345 glPolygonMode(GL_FRONT, GL_LINE);
1347 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
1349 glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
1350 glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiff);
1351 glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);
1353 glEnable(GL_SMOOTH);
1354 glEnable(GL_LIGHTING);
1355 glEnable(GL_LIGHT0);
1357 glDepthFunc(GL_LESS);
1358 glEnable(GL_DEPTH_TEST);
1360 glCullFace(GL_BACK);
1361 glEnable(GL_CULL_FACE);
1363 pState->DLStart = InitGLDisplayLists();
1367 static void SetCamera(RENDER_STATE* pState)
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
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.
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.
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
1397 float ElevRad = pState->CameraElev * PI / 180.0f;
1408 const PATTERN_INFO* pPattern = pState->pPattern;
1410 glMatrixMode(GL_PROJECTION);
1413 for (i = 0; i < pPattern->ThrowLen; i++)
1414 H = max(H, pPattern->pThrowInfo[i].FromPos.y);
1416 H += pPattern->Height;
1418 ElevRad = pState->CameraElev * PI / 180.0f;
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;
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;
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) |
1447 d = cosf(ba) * sinf(ta) - cosf(ta) * sinf(ba);
1448 a = (cosf(ba) * (by - ty) - sinf(ba) * (bz - tz)) / d;
1450 ey = ty + a * sinf(ta);
1451 ez = tz + a * cosf(ta);
1453 /* now work back from the eye point to get the lookat location */
1455 cy = ey - ez * tanf(ElevRad);
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
1460 d = sqrtf(ez * ez + (cy - ey) * (cy - ey));
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);
1465 glMatrixMode(GL_MODELVIEW);
1469 void ResizeGL(RENDER_STATE* pState, int w, int h)
1471 glViewport(0, 0, w, h);
1472 pState->AspectRatio = (float) w / h;
1477 /* Determine the angle at the vertex of a triangle given the length of the
1480 static double CosineRule(double a, double b, double c)
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)
1489 return 180.0 * acos(cosang) / PI;
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
1499 static void InterpolateVertex(
1500 const float* v1, const float* v2, float t, float* result)
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;
1508 static void SetGLVertex(const float* v, float rad)
1510 float Len = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
1512 if (Len >= 1.0e-10f)
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);
1522 static void SphereSegment(
1523 const float* v1, const float* v2, const float* v3, float r, int Levels)
1527 for (i = 0; i < Levels; i++)
1529 float A[3], B[3], C[3], D[3];
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);
1536 glBegin(GL_TRIANGLE_STRIP);
1541 for (j = 1; j <= i; j++)
1545 InterpolateVertex(B, A, (float) j / (i + 1), v);
1548 InterpolateVertex(C, D, (float) j / i, v);
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. */
1564 static void DrawSphere(float rad)
1567 float v1[3], v2[3], v3[3];
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);
1575 SphereSegment(v2, v1, v3, rad, Levels);
1577 v1[0] = v3[2] = -1.0f;
1578 SphereSegment(v2, v1, v3, rad, Levels);
1581 SphereSegment(v1, v2, v3, rad, Levels);
1585 static void DrawRing(void)
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);
1593 gluCylinder(pQuad, 1.0f, 1.0f, w, Facets, 1);
1594 gluQuadricOrientation(pQuad, GLU_INSIDE);
1596 gluCylinder(pQuad, 0.7f, 0.7f, w, Facets, 1);
1597 gluQuadricOrientation(pQuad, GLU_OUTSIDE);
1599 glTranslatef(0.0f, 0.0f, w);
1600 gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
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);
1606 gluDeleteQuadric(pQuad);
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. */
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};
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]));
1628 for (i = 0; i < n; i += 2)
1630 float a1 = i * PI * 2.0f / n;
1631 float a2 = (i + 1) * PI * 2.0f / n;
1633 glBegin(GL_TRIANGLE_STRIP);
1634 for (j = 1; j < 4; j++)
1636 glNormal3f(cosf(na[j]) * cosf(a1),
1637 cosf(na[j]) * sinf(a1), sinf(na[j]));
1639 glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1641 glNormal3f(cosf(na[j]) * cosf(a2),
1642 cosf(na[j]) * sinf(a2), sinf(na[j]));
1644 glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1649 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, HandleCol);
1651 for (i = 1; i < n; i += 2)
1653 float a1 = i * PI * 2.0f / n;
1654 float a2 = (i + 1) * PI * 2.0f / n;
1656 glBegin(GL_TRIANGLE_STRIP);
1657 for (j = 1; j < 4; j++)
1659 glNormal3f(cosf(na[j]) * cosf(a1),
1660 cosf(na[j]) * sinf(a1), sinf(na[j]));
1662 glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1664 glNormal3f(cosf(na[j]) * cosf(a2),
1665 cosf(na[j]) * sinf(a2), sinf(na[j]));
1667 glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1672 pQuad = gluNewQuadric();
1673 glTranslatef(0.0f, 0.0f, z[0]);
1674 gluCylinder(pQuad, r[0], r[1], z[1] - z[0], n, 1);
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);
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. */
1689 static int InitGLDisplayLists(void)
1691 int s = glGenLists(6);
1694 glNewList(s + DL_BALL, GL_COMPILE);
1695 DrawSphere(BallRad);
1698 glNewList(s + DL_CLUB, GL_COMPILE);
1702 glNewList(s + DL_RING, GL_COMPILE);
1706 pQuad = gluNewQuadric();
1707 gluQuadricNormals(pQuad, GLU_SMOOTH);
1709 glNewList(s + DL_TORSO, GL_COMPILE);
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);
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);
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);
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);
1736 glTranslatef(0.0f, 0.0f, .3f);
1737 gluDisk(pQuad, 0.0, 0.5, 15, 1);
1741 glNewList(s + DL_UPPERARM, GL_COMPILE);
1742 gluQuadricNormals(pQuad, GLU_SMOOTH);
1743 gluQuadricDrawStyle(pQuad, GLU_FILL);
1744 gluSphere(pQuad, 0.3, 12, 8);
1746 gluCylinder(pQuad, 0.3, 0.3, UArmLen, 12, 1);
1747 glTranslatef(0.0f, 0.0f, UArmLen);
1748 gluSphere(pQuad, 0.3, 12, 8);
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);
1757 gluDeleteQuadric(pQuad);
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
1772 void DrawArm(RENDER_STATE* pState, float TimePos, int Left)
1775 float x, y, len, len2, ang, ang2;
1777 GetHandPosition(pState->pPattern, Left, TimePos, &Pos);
1779 x = Pos.x + (Left ? -ShoulderPos[0] : ShoulderPos[0]);
1780 y = Pos.y - ShoulderPos[1];
1783 len = sqrtf(x * x + y * y + ShoulderPos[2] * ShoulderPos[2]);
1784 len2 = sqrtf(x * x + ShoulderPos[2] * ShoulderPos[2]);
1786 ang = (float) CosineRule(UArmLen, len, LArmLen);
1787 ang2 = (float) CosineRule(UArmLen, LArmLen, len);
1789 if (ang == 0.0 && ang2 == 0)
1794 glTranslatef(Left ? ShoulderPos[0] : -ShoulderPos[0], ShoulderPos[1],
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);
1802 glRotatef((float)(ang2 - 180.0), 1.0f, 0.0f, 0.f);
1803 glCallList(DL_FOREARM + pState->DLStart);
1808 void DrawGLScene(RENDER_STATE* pState)
1810 float Time = pState->Time;
1811 int nCols = sizeof(Cols) / sizeof(Cols[0]);
1814 PATTERN_INFO* pPattern = pState->pPattern;
1816 glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
1818 glMatrixMode(GL_MODELVIEW);
1820 glTranslatef(5.0f * sinf(pState->TranslateAngle), 0.0f, 0.0f);
1822 gltrackball_rotate (pState->trackball);
1824 glRotatef(pState->SpinAngle, 0.0f, 1.0f, 0.0f);
1825 glTranslatef(0.0, 0.0, -1.0f);
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);
1831 for (i = 0; i < pPattern->Objects; i++)
1835 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Cols[i % nCols]);
1838 switch (pPattern->pObjectInfo[i].ObjectType)
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);
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);
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);
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);
1879 static int RandInRange(int Min, int Max)
1881 return Min + random() % (1 + Max - Min);
1885 extern void UpdatePattern(
1886 RENDER_STATE* pState, int MinBalls, int MaxBalls,
1887 int MinHeightInc, int MaxHeightInc)
1889 if (pState->pPattern != NULL)
1890 ReleasePatternInfo(pState->pPattern);
1892 pState->pPattern = (PATTERN_INFO*) malloc(sizeof(PATTERN_INFO));
1894 if ((random() % 3) == 1)
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);
1905 int ballcount, maxweight;
1906 const int RandPatternLen = 1500;
1908 ballcount = RandInRange(MinBalls, MaxBalls);
1909 maxweight = ballcount + RandInRange(MinHeightInc, MaxHeightInc);
1911 pRand = Generate(RandPatternLen, maxweight, ballcount);
1912 InitPatternInfo(pState->pPattern, pRand, NULL, RandPatternLen);
1916 pState->CameraElev = 50.0f - random() % 90;
1917 pState->TranslateAngle = random() % 360;
1918 pState->SpinAngle = random() % 360;
1919 pState->Time = 50.0f;
1924 /*******************************************************************************
1926 * XScreenSaver Configuration
1928 ******************************************************************************/
1930 extern XtAppContext app;
1934 GLXContext* glxContext;
1935 RENDER_STATE RenderState;
1936 float CurrentFrameRate;
1937 unsigned FramesSinceSync;
1938 unsigned LastSyncTime;
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;
1951 static XrmOptionDescRec Options[] =
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},
1963 static argtype Vars[] =
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},
1975 ModeSpecOpt SWITCH_OPTS = {countof(Options), Options, countof(Vars), Vars};
1978 void Juggler3D_HackReshapeEvent(ModeInfo *mi, int width, int height)
1980 JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
1981 ResizeGL(&pConfig->RenderState, width, height);
1985 void Juggler3D_HackInitEvent(ModeInfo* mi)
1987 JUGGLER3D_CONFIG* pConfig;
1989 if (pConfigInfo == NULL)
1991 /* Apply suitable bounds checks to the input parameters */
1992 MaxObjects = max(3, min(MaxObjects, 36));
1993 MinObjects = max(3, min(MinObjects, MaxObjects));
1995 MaxHeightInc = max(1, min(MaxHeightInc, 32));
1996 MinHeightInc = max(1, min(MinHeightInc, MaxHeightInc));
1998 pConfigInfo = (JUGGLER3D_CONFIG*) calloc(
1999 MI_NUM_SCREENS(mi), sizeof(JUGGLER3D_CONFIG));
2000 if (pConfigInfo == NULL)
2002 fprintf(stderr, "%s: out of memory\n", progname);
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));
2014 UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2015 MinHeightInc, MaxHeightInc);
2017 Juggler3D_HackReshapeEvent(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2021 void Juggler3D_HackDrawEvent(ModeInfo* mi)
2023 JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2024 Display* pDisplay = MI_DISPLAY(mi);
2025 Window hwnd = MI_WINDOW(mi);
2027 if (pConfig->glxContext == NULL)
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). */
2036 if (pConfig->FramesSinceSync >= 1 * (int) pConfig->CurrentFrameRate)
2038 struct timeval tvnow;
2041 # ifdef GETTIMEOFDAY_TWO_ARGS
2042 struct timezone tzp;
2043 gettimeofday(&tvnow, &tzp);
2045 gettimeofday(&tvnow);
2048 now = (unsigned) (tvnow.tv_sec * 1000000 + tvnow.tv_usec);
2049 if (pConfig->FramesSinceSync == 0)
2051 pConfig->LastSyncTime = now;
2055 unsigned Delta = now - pConfig->LastSyncTime;
2058 pConfig->LastSyncTime = now;
2059 pConfig->CurrentFrameRate =
2060 (pConfig->FramesSinceSync * 1.0e6f) / Delta;
2061 pConfig->FramesSinceSync = 0;
2066 pConfig->FramesSinceSync++;
2068 if (pConfig->RenderState.Time > 150.0f)
2070 UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2071 MinHeightInc, MaxHeightInc);
2073 DrawGLScene(&pConfig->RenderState);
2075 if (pConfig->CurrentFrameRate > 1.0e-6f)
2077 pConfig->RenderState.Time += JuggleSpeed / pConfig->CurrentFrameRate;
2078 pConfig->RenderState.SpinAngle += SpinSpeed / pConfig->CurrentFrameRate;
2079 pConfig->RenderState.TranslateAngle +=
2080 TranslateSpeed / pConfig->CurrentFrameRate;
2087 glXSwapBuffers(pDisplay, hwnd);
2091 Bool Juggler3D_HackHandleEvent(ModeInfo* mi, XEvent* pEvent)
2093 JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2094 RENDER_STATE* pState = &pConfig->RenderState;
2096 if (pEvent->xany.type == ButtonPress &&
2097 pEvent->xbutton.button == Button1)
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));
2105 else if (pEvent->xany.type == ButtonRelease &&
2106 pEvent->xbutton.button == Button1)
2108 pState->button_down_p = False;
2111 else if (pEvent->xany.type == ButtonPress &&
2112 (pEvent->xbutton.button == Button4 ||
2113 pEvent->xbutton.button == Button5))
2115 gltrackball_mousewheel (pState->trackball, pEvent->xbutton.button, 2,
2116 !pEvent->xbutton.state);
2119 else if (pEvent->xany.type == MotionNotify &&
2120 pState->button_down_p)
2122 gltrackball_track (pState->trackball,
2123 pEvent->xmotion.x, pEvent->xmotion.y,
2124 MI_WIDTH (mi), MI_HEIGHT (mi));
2127 else if (pEvent->xany.type == KeyPress)
2131 int count = XLookupString(&pEvent->xkey, str, 20, &Key, 0);
2135 UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2136 MinHeightInc, MaxHeightInc);