1
/* Juggler3D, Copyright (c) 2005-2008 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. */
1
/* juggle, Copyright (c) 1996-2009 Tim Auckland <tda10.geo@yahoo.com>
2
* and Jamie Zawinski <jwz@jwz.org>
4
* Permission to use, copy, modify, and distribute this software and its
5
* documentation for any purpose and without fee is hereby granted,
6
* provided that the above copyright notice appear in all copies and that
7
* both that copyright notice and this permission notice appear in
8
* supporting documentation.
10
* This file is provided AS IS with no warranties of any kind. The author
11
* shall have no liability with respect to the infringement of copyrights,
12
* trade secrets or any patents by this file or any part thereof. In no
13
* event will the author be liable for any lost revenue or profits or
14
* other special, indirect and consequential damages.
16
* NOTE: this program was originally called "juggle" and was 2D Xlib.
17
* There was another program called "juggler3d" that was OpenGL.
18
* In 2009, jwz converted "juggle" to OpenGL and renamed
19
* "juggle" to "juggler3d". The old "juggler3d" hack is gone.
22
* 09-Aug-2009: jwz: converted from Xlib to OpenGL.
23
* 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
24
* Add -rings, -bballs. Add -describe. Finally made
25
* live pattern updates possible. Add refill_juggle(),
26
* change_juggle() and reshape_juggle(). Make
27
* init_juggle() non-destructive. Reorder erase/draw
28
* operations. Update xscreensaver xml and manpage.
29
* 15-Nov-2004: [TDA] Fix all memory leaks.
30
* 12-Nov-2004: [TDA] Add -torches and another new trail
31
* implementation, so that different objects can have
32
* different length trails.
33
* 11-Nov-2004: [TDA] Clap when all the balls are in the air.
34
* 10-Nov-2004: [TDA] Display pattern name converted to hight
36
* 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
37
* 02-Sep-2003: Non-real time to see what is happening without a
38
* strobe effect for slow machines.
39
* 01-Nov-2000: Allocation checks
45
* Implement the anonymously promised -uni option.
50
* Notes on Adam Chalcraft Juggling Notation (used by permission)
51
* a-> Adam's notation s-> Site swap (Cambridge) notation
53
* To define a map from a-notation to s-notation ("site-swap"), both
54
* of which look like doubly infinite sequences of natural numbers. In
55
* s-notation, there is a restriction on what is allowed, namely for
56
* the sequence s_n, the associated function f(n)=n+s_n must be a
57
* bijection. In a-notation, there is no restriction.
59
* To go from a-notation to s-notation, you start by mapping each a_n
60
* to a permutation of N, the natural numbers.
63
* 1 -> (10) [i.e. f(1)=0, f(0)=1]
64
* 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
65
* 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
68
* Then for each n, you look at how long 0 takes to get back to 0
69
* again and you call this t_n. If a_n=0, for example, then since the
70
* identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
71
* a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
72
* next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
73
* following the 1. Finally, set s_n = t_n - 1.
75
* To give some examples, it helps to have a notation for cyclic
76
* sequences. By (123), for example, I mean ...123123123123... . Now
77
* under the a-notation -> s-notation mapping we have some familiar
80
* (0)->(0), (1)->(1), (2)->(2) etc.
81
* (21)->(31), (31)->(51), (41)->(71) etc.
82
* (10)->(20), (20)->(40), (30)->(60) etc.
83
* (331)->(441), (312)->(612), (303)->(504), (321)->(531)
84
* (43)->(53), (434)->(534), (433)->(633)
87
* In general, the number of balls is the *average* of the s-notation,
88
* and the *maximum* of the a-notation. Another theorem is that the
89
* minimum values in the a-notation and the s-notation and equal, and
90
* preserved in the same positions.
92
* The usefulness of a-notation is the fact that there are no
93
* restrictions on what is allowed. This makes random juggle
94
* generation much easier. It also makes enumeration very
95
* easy. Another handy feature is computing changes. Suppose you can
96
* do (5) and want a neat change up to (771) in s-notation [Mike Day
97
* actually needed this example!]. Write them both in a-notation,
98
* which gives (5) and (551). Now concatenate them (in general, there
99
* may be more than one way to do this, but not in this example), to
102
* ...55555555551551551551551...
104
* Now convert back to s-notation, to get
106
* ...55555566771771771771771...
108
* So the answer is to do two 6 throws and then go straight into
109
* (771). Coming back down of course,
111
* ...5515515515515515555555555...
115
* ...7717717717716615555555555...
117
* so the answer is to do a single 661 and then drop straight down to
120
* [The number of balls in the generated pattern occasionally changes.
121
* In order to decrease the number of balls I had to introduce a new
122
* symbol into the Adam notation, [*] which means 'lose the current
126
/* This code uses so many linked lists it's worth having a built-in
130
# define DEFAULTS "*delay: 10000 \n" \
134
"*titleFont: -*-helvetica-bold-r-normal-*-180-*\n" \
135
"*showFPS: False \n" \
136
"*wireframe: False \n" \
138
# define refresh_juggle 0
11
140
#define countof(x) (sizeof((x))/sizeof((*x)))
14
"*delay: 20000\n*showFPS: False\n*wireframe: False\n"
16
# define refresh_juggler3d 0
17
# define release_juggler3d 0
18
142
#include "xlockmore.h"
19
146
#include "gltrackball.h"
147
#include "glxfonts.h"
21
150
#ifdef USE_GL /* whole file */
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) */
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)))
38
#define max(a, b) ((a) > (b) ? (a) : (b))
39
#define min(a, b) ((a) < (b) ? (a) : (b))
42
/******************************************************************************
44
* The code is broadly split into the following parts:
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.
56
*****************************************************************************/
59
/*****************************************************************************
63
*****************************************************************************/
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.
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. */
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
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
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. */
121
/* OBJECT_POSITION works with the array of THROW_INFOs to allow us to determine
122
* exactly where an object or hand is.
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
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. */
132
#define OBJECT_DEFAULT 0
133
#define OBJECT_BALL 1
134
#define OBJECT_CLUB 2
135
#define OBJECT_RING 3
146
/* PATTERN_INFO is the main structure that holds the information about a
149
* pThrowInfo is an array of ThrowLen elements that describes each throw in the
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
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. */
161
THROW_INFO* pThrowInfo;
164
OBJECT_POSITION* pObjectInfo;
167
OBJECT_POSITION LeftHand;
168
OBJECT_POSITION RightHand;
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. */
181
#define HAS_FROM_POS 1
199
/* RENDER_STATE is used to co-ordinate the OpenGL rendering of the juggler and
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
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.
215
PATTERN_INFO* pPattern;
221
float TranslateAngle;
224
trackball_state *trackball;
230
/*****************************************************************************
234
****************************************************************************
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.
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).
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.)
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.
260
static const float CARRY_TIME = 0.56f;
261
static const float PI = 3.14159265358979f;
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. */
267
static float GetBallTwistAmount(const THROW_INFO* pThrow)
269
if (pThrow->FromPos.x > pThrow->ToPos.x)
270
return 18.0f * powf(pThrow->TimeInAir, 1.5);
272
return -18.0f * powf(pThrow->TimeInAir, 1.5);
276
static float NormaliseAngle(float Ang)
280
int i = (int) (Ang + 180.0f) / 360;
281
return Ang - 360.0f * i;
285
int i = (int)(180.0f - Ang) / 360;
286
return Ang + i * 360.0f;
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:
300
static POS InterpolatePosition(
301
const POS* pP0, const POS* pV0, const POS* pP1, const POS* pV1,
305
float a, b, c, d, tt, tc;
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
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.
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.
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
333
* writing in terms of t
334
* tt = (-B + (B ^ 2 + 4At) ^ 0.5) / 2A
336
* tt = ((1 + 4At) ^ 0.5 - 1) / 2A */
338
float A = 2.0f * (TLen - tc) / (tc * tc);
341
t = tc - (sqrtf(1.0f + 4.0f * A * (TLen - t)) - 1.0f) / (2.0f * A);
343
t = (sqrtf(1.0f + 4.0f * A * t) - 1.0f) / (2.0f * A);
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.
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
359
* statisfy the boundary conditions:
360
* P(0) = p0, P(TLen) = p1, P'(0) = v0 and P'(TLen) = v1 */
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);
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;
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;
382
static POS InterpolateCarry(
383
const THROW_INFO* pThrow, const THROW_INFO* pNext, float t)
385
float CT = CARRY_TIME + pThrow->TotalTime - pThrow->TimeInAir;
386
return InterpolatePosition(&pThrow->ToPos, &pThrow->ToVelocity,
387
&pNext->FromPos, &pNext->FromVelocity, CT, t);
391
/* Determine the position of the hand at a point in time. */
393
static void GetHandPosition(
394
PATTERN_INFO* pPattern, int RightHand, float Time, POS* pPos)
396
OBJECT_POSITION* pObj =
397
RightHand == 0 ? &pPattern->LeftHand : &pPattern->RightHand;
398
THROW_INFO* pLastThrow;
400
/* Upon entry, the throw information for the relevant hand may be out of
401
* sync. Therefore we advance through the pattern if required. */
403
while (pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand + pObj->TimeOffset
406
int w = pPattern->pThrowInfo[pObj->ThrowIndex].NextForHand;
407
pObj->TimeOffset += w;
408
pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
411
pLastThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
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)
419
pPos->x = pLastThrow->FromPos.x;
420
pPos->y = pLastThrow->FromPos.y;
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
432
THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
433
(pObj->ThrowIndex + pLastThrow->NextForHand) % pPattern->ThrowLen];
435
THROW_INFO* pNextThrownFrom =
436
&pPattern->pThrowInfo[pNextThrow->PrevThrow];
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. */
442
float tc = CARRY_TIME +
443
pNextThrownFrom->TotalTime - pNextThrownFrom->TimeInAir;
445
Time -= pObj->TimeOffset;
447
if (Time > pLastThrow->NextForHand - tc)
449
/* carrying this ball to it's new location */
450
*pPos = InterpolateCarry(pNextThrownFrom,
451
pNextThrow, (Time - (pLastThrow->NextForHand - tc)));
455
/* going for next catch */
456
*pPos = InterpolatePosition(
457
&pLastThrow->FromPos, &pLastThrow->FromVelocity,
458
&pNextThrownFrom->ToPos, &pNextThrownFrom->ToVelocity,
459
pLastThrow->NextForHand - tc, Time);
465
static float SinDeg(float AngInDegrees)
467
return sinf(AngInDegrees * PI / 180.0f);
471
static float CosDeg(float AngInDegrees)
473
return cosf(AngInDegrees * PI / 180.0f);
477
/* Offset the specified position to get the centre of the object based on the
478
* the handle length and the current orientation */
480
static void OffsetHandlePosition(const POS* pPos, float HandleLen, POS* pResult)
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;
490
static void GetObjectPosition(
491
PATTERN_INFO* pPattern, int Obj, float Time, float HandleLen, POS* pPos)
493
OBJECT_POSITION* pObj = &pPattern->pObjectInfo[Obj];
496
/* Move through the pattern, if required, such that pThrow corresponds to
497
* the current throw for this object. */
499
while (pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime + pObj->TimeOffset
502
int w = pPattern->pThrowInfo[pObj->ThrowIndex].TotalTime;
503
pObj->TimeOffset += w;
504
pObj->TotalTwist = NormaliseAngle(pObj->TotalTwist +
505
GetBallTwistAmount(&pPattern->pThrowInfo[pObj->ThrowIndex]));
507
pObj->ThrowIndex = (pObj->ThrowIndex + w) % pPattern->ThrowLen;
510
pThrow = &pPattern->pThrowInfo[pObj->ThrowIndex];
512
if (pThrow->TimeInAir == 2 || pThrow->TimeInAir == 0)
514
*pPos = pThrow->FromPos;
515
OffsetHandlePosition(pPos, HandleLen, pPos);
519
float tc = pThrow->TimeInAir - CARRY_TIME;
520
float BallTwist = GetBallTwistAmount(pThrow);
521
Time -= pObj->TimeOffset;
530
OffsetHandlePosition(&pThrow->FromPos, HandleLen, &From);
531
OffsetHandlePosition(&pThrow->ToPos, HandleLen, &To);
533
b = (To.y - From.y) / tc + pPattern->Alpha * tc;
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;
539
if (pObj->ObjectType == OBJECT_BALL)
540
pPos->Rot = pObj->TotalTwist + t * BallTwist;
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.
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. */
568
float RotAmt = NormaliseAngle(To.Rot - From.Rot);
572
To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
575
else if (RotAmt > 90.0f)
577
To.Elev += 180 - 2 * NormaliseAngle(To.Elev);
581
pPos->Rot = From.Rot + t * RotAmt;
584
pPos->Elev = (1.0f - t) * From.Elev + t * To.Elev;
589
THROW_INFO* pNextThrow = &pPattern->pThrowInfo[
590
(pObj->ThrowIndex + pThrow->TotalTime) % pPattern->ThrowLen];
592
*pPos = InterpolateCarry(pThrow, pNextThrow, Time - tc);
594
if (pObj->ObjectType == OBJECT_BALL)
595
pPos->Rot = pObj->TotalTwist + BallTwist;
597
OffsetHandlePosition(pPos, HandleLen, pPos);
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.
611
* Basically we pick a height we'd like to see the biggest throw reach and then
612
* adjust Alpha to meet this. */
614
static void SetHeightAndAlpha(PATTERN_INFO* pPattern,
615
const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
623
for (i = 0; i < Len; i++)
624
MaxW = max(MaxW, Site[i]);
628
for (i = 0; i < Len; i++)
629
MaxW = max(MaxW, pExtInfo[i].Weight);
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
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);
648
/* Where positions and spin info is not specified, generate suitable default
651
static int GetDefaultSpins(int Weight)
664
static void GetDefaultFromPosition(unsigned char Side, int Weight, POS* pPos)
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;
671
pPos->x = Side? 0.24f : -0.24f;
673
pPos->y = (Weight == 2 || Weight == 0) ? -0.25f : 0.0f;
675
pPos->Rot = (Weight % 2 == 0 ? -23.5f : 27.0f) * (Side ? -1.0f : 1.0f);
677
pPos->Elev = Weight == 1 ? -30.0f : 0.0f;
682
static void GetDefaultToPosition(unsigned char Side, int Weight, POS* pPos)
685
pPos->x = Side ? -1.0f : 1.0f;
686
else if (Weight % 2 == 0)
687
pPos->x = Side ? 2.8f : -2.8f;
689
pPos->x = Side? -3.1f : 3.1f;
693
pPos->Rot = (Side ? -35.0f : 35.0f) * (Weight % 2 == 0 ? -1.0f : 1.0f);
699
pPos->Elev = 360.0f - 50.0f;
701
pPos->Elev = 720.0f - 50.0f;
703
pPos->Elev = 360.0f * GetDefaultSpins(Weight) - 50.0f;
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. */
714
static void InitPatternInfo(PATTERN_INFO* pPattern,
715
const int* Site, const EXT_SITE_INFO* pExtInfo, int Len)
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;
722
THROW_INFO* pInfo = (THROW_INFO*) calloc(InfoLen, sizeof(THROW_INFO));
724
unsigned char* pUsed;
726
pPattern->MaxWeight = 0;
727
pPattern->ThrowLen = InfoLen;
728
pPattern->pThrowInfo = pInfo;
730
SetHeightAndAlpha(pPattern, Site, pExtInfo, Len);
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. */
740
for (i = 0; i < InfoLen; i++)
743
int w = pExtInfo != NULL ? pExtInfo[i % Len].Weight : Site[i % Len];
745
pInfo[i].TotalTime = pInfo[i].TimeInAir = w;
746
pInfo[(w + i) % Len].PrevThrow = i;
748
/* work out where we are throwing this object from and where it's going
751
if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_FROM_POS) == 0)
752
GetDefaultFromPosition(i % 2, w, &pInfo[i].FromPos);
754
pInfo[i].FromPos = pExtInfo[i % Len].FromPos;
756
if (pExtInfo == NULL || (pExtInfo[i % Len].Flags & HAS_TO_POS) == 0)
757
GetDefaultToPosition(i % 2, w, &pInfo[i].ToPos);
759
pInfo[i].ToPos = pExtInfo[i % Len].ToPos;
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.
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
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)
779
* The velocity in the x direction is constant and can be simply
781
* x' = (x1 - x0) / t1
782
* where x0 and x1 are the start and end x-positions respectively.
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;
801
if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SNATCH) != 0)
803
pInfo[i].ToVelocity.x = pExtInfo[i % Len].SnatchX;
804
pInfo[i].ToVelocity.y = pExtInfo[i % Len].SnatchY;
807
if (pExtInfo != NULL && (pExtInfo[i % Len].Flags & HAS_SPINS) != 0)
809
pInfo[i].ToPos.Elev = 360.0f * pExtInfo[i % Len].Spins +
810
NormaliseAngle(pInfo[i].ToPos.Elev);
814
if (w > pPattern->MaxWeight)
815
pPattern->MaxWeight = w;
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. */
825
for (i = 0; i < InfoLen; i++)
827
if (pInfo[i].TimeInAir != 2)
829
int Next = pInfo[i].TimeInAir + i;
830
while (pInfo[Next % InfoLen].TimeInAir == 2)
833
pInfo[i].TotalTime += 2;
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;
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. */
845
for (i = 0; i < InfoLen; i++)
847
if (pInfo[i].TimeInAir != 0 && pInfo[i].TimeInAir != 2)
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
856
while (pInfo[(i + Wait) % InfoLen].TimeInAir == 2 ||
857
pInfo[(i + Wait) % InfoLen].TimeInAir == 0)
861
pInfo[i].NextForHand = Wait;
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;
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. */
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));
884
for (i = 0; i < pPattern->MaxWeight; i++)
887
for (i = 0; i < pPattern->MaxWeight; i++)
889
int w = pInfo[i % InfoLen].TimeInAir;
890
if (pUsed[i] == 0 && w != 0)
893
pPattern->pObjectInfo[Objects].TimeOffset = i;
894
pPattern->pObjectInfo[Objects].ThrowIndex = i % InfoLen;
895
pPattern->pObjectInfo[Objects].TotalTwist = 0.0f;
897
if (pExtInfo != NULL &&
898
pExtInfo[i % Len].ObjectType != OBJECT_DEFAULT)
900
pPattern->pObjectInfo[Objects].ObjectType =
901
pExtInfo[i % Len].ObjectType;
905
pPattern->pObjectInfo[Objects].ObjectType = (1 + random() % 3);
909
if (w + i < pPattern->MaxWeight)
914
pPattern->LeftHand.TimeOffset = pPattern->LeftHand.ThrowIndex = 0;
915
pPattern->RightHand.TimeOffset = pPattern->RightHand.ThrowIndex = 1;
921
static void ReleasePatternInfo(PATTERN_INFO* pPattern)
923
free(pPattern->pObjectInfo);
924
free(pPattern->pThrowInfo);
928
/*****************************************************************************
932
****************************************************************************/
934
/* Generate a random site swap. We assume that MaxWeight >= ObjCount and
935
* Len >= MaxWeight. */
937
static int* Generate(int Len, int MaxWeight, int ObjCount)
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));
945
for (i = 0; i < Len; i++)
946
Weight[i] = Used[i] = -1;
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. */
954
for (j = 0; j < MaxWeight; j++)
957
Options[nOpts++] = j;
960
Used[Options[random() % nOpts]] = -2;
964
/* Now work our way through the pattern moving throws into an available
965
* landing positions. */
966
for (i = 0; i < Len; i++)
970
/* patch up holes in the pattern to zeros */
976
/* Work out the possible places where a throw can land and pick a
977
* weight at random. */
981
for (j = 0 ; j <= MaxWeight; j++)
983
if (Used[(i + j) % Len] == -1)
984
Options[nOpts++] = j;
987
w = Options[random() % nOpts];
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. */
997
Used[(i + w) % Len] = 1;
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. */
1013
/* The position text looks something like (x,y,z[,rot[,elev]])
1014
* where the stuff in square brackets is optional */
1016
static unsigned char ParsePositionText(const char** ppch, POS* pPos)
1018
const char* pch = *ppch;
1027
Nums[2] = &pPos->Rot;
1028
Nums[3] = &pPos->Elev;
1039
for (i = 0; OK && i < 4; i++)
1044
while (*pch != ',' && *pch != '\0' && *pch != ')' && *pch != ' ')
1048
if (szTemp[0] != '\0')
1049
*Nums[i] = (float) atof(szTemp);
1058
else if (*pch == ')')
1081
static EXT_SITE_INFO* ParsePattern(const char* Site, int* pLen)
1083
const char* pch = Site;
1085
EXT_SITE_INFO* pInfo = NULL;
1086
unsigned char OK = 1;
1088
while (OK && *pch != 0)
1093
while (*pch == ' ') pch++;
1098
Info.Weight = *pch >= 'A' ? *pch + 10 - 'A' : *pch - '0';
1100
/* parse object type */
1104
while (*pch == ' ') pch++;
1106
if (*pch == 'b' || *pch == 'B')
1108
Info.ObjectType = OBJECT_BALL;
1111
else if (*pch == 'c' || *pch == 'C')
1113
Info.ObjectType = OBJECT_CLUB;
1116
else if (*pch == 'r' || *pch == 'R')
1118
Info.ObjectType = OBJECT_RING;
1121
else if (*pch == 'd' || *pch == 'D')
1123
Info.ObjectType = OBJECT_DEFAULT;
1128
Info.ObjectType = OBJECT_DEFAULT;
1132
/* Parse from position */
1135
while (*pch == ' ') pch++;
1139
GetDefaultFromPosition(Len % 2, Info.Weight, &Info.FromPos);
1140
Info.Flags |= HAS_FROM_POS;
1141
OK = ParsePositionText(&pch, &Info.FromPos);
1145
/* Parse to position */
1148
while (*pch == ' ') pch++;
1152
GetDefaultToPosition(Len % 2, Info.Weight, &Info.ToPos);
1153
Info.Flags |= HAS_TO_POS;
1154
OK = ParsePositionText(&pch, &Info.ToPos);
1161
while (*pch == ' ') pch++;
1166
Info.Flags |= HAS_SNATCH;
1167
OK = ParsePositionText(&pch, &Snatch);
1168
Info.SnatchX = Snatch.x;
1169
Info.SnatchY = Snatch.y;
1176
while (*pch == ' ') pch++;
1182
while (*pch >= '0' && *pch <= '9')
1185
Info.Spins = Info.Spins * 10 + *pch - '0';
1190
Info.Spins = GetDefaultSpins(Info.Weight);
1192
Info.Flags |= HAS_SPINS;
1198
pInfo = (EXT_SITE_INFO*) malloc(sizeof(EXT_SITE_INFO));
1200
pInfo = (EXT_SITE_INFO*) realloc(pInfo, (Len + 1) * sizeof(EXT_SITE_INFO));
1207
if (!OK && pInfo != NULL)
1219
/*****************************************************************************
1221
* Juggle Saver Patterns
1223
*****************************************************************************
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.
1230
static const char* PatternText[] =
1232
"9b@(-2.5,0,-70,40)>(2.5,0,70)*2 1b@(1,0,10)>(-1,0,-10)",
1234
"3B@(1,-0.4)>(2,4.2)/(-2,1)3B@(-1.8,4.4)>(-2.1,0)",
1236
"7c@(-2,0,-20)>(1.2,0,-5)7c@(2,0,20)>(-1.2,0,5)",
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)",
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)",
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)",
1248
"9c@(-2.5,0,-70,40)>(2.5,0,70)*2 1c@(1,0,10)>(-1,0,-10)*0",
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)",
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",
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",
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)"
1267
/*****************************************************************************
1271
*****************************************************************************/
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};
1283
static const float BallRad = 0.34f;
1284
static const float UArmLen = 1.9f;
1285
static const float LArmLen = 2.3f;
1291
#define DL_FOREARM 4
1292
#define DL_UPPERARM 5
1294
static const float AltCols[][4] =
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},
1310
static const float Cols[][4] =
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 */
1326
static int InitGLDisplayLists(void);
1329
static void InitGLSettings(RENDER_STATE* pState, int WireFrame)
1331
memset(pState, 0, sizeof(RENDER_STATE));
1333
pState->trackball = gltrackball_init ();
1336
glPolygonMode(GL_FRONT, GL_LINE);
1338
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
1340
glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
1341
glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDiff);
1342
glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);
1344
glEnable(GL_SMOOTH);
1345
glEnable(GL_LIGHTING);
1346
glEnable(GL_LIGHT0);
1348
glDepthFunc(GL_LESS);
1349
glEnable(GL_DEPTH_TEST);
1351
glCullFace(GL_BACK);
1352
glEnable(GL_CULL_FACE);
1354
pState->DLStart = InitGLDisplayLists();
1358
static void SetCamera(RENDER_STATE* pState)
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
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.
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.
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
1388
float ElevRad = pState->CameraElev * PI / 180.0f;
1399
const PATTERN_INFO* pPattern = pState->pPattern;
1401
glMatrixMode(GL_PROJECTION);
1404
for (i = 0; i < pPattern->ThrowLen; i++)
1405
H = max(H, pPattern->pThrowInfo[i].FromPos.y);
1407
H += pPattern->Height;
1409
ElevRad = pState->CameraElev * PI / 180.0f;
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;
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;
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) |
1438
d = cosf(ba) * sinf(ta) - cosf(ta) * sinf(ba);
1439
a = (cosf(ba) * (by - ty) - sinf(ba) * (bz - tz)) / d;
1441
ey = ty + a * sinf(ta);
1442
ez = tz + a * cosf(ta);
1444
/* now work back from the eye point to get the lookat location */
1446
cy = ey - ez * tanf(ElevRad);
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
1451
d = sqrtf(ez * ez + (cy - ey) * (cy - ey));
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);
1456
glMatrixMode(GL_MODELVIEW);
1460
static void ResizeGL(RENDER_STATE* pState, int w, int h)
1462
glViewport(0, 0, w, h);
1463
pState->AspectRatio = (float) w / h;
1468
/* Determine the angle at the vertex of a triangle given the length of the
1471
static double CosineRule(double a, double b, double c)
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)
1480
return 180.0 * acos(cosang) / PI;
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
1490
static void InterpolateVertex(
1491
const float* v1, const float* v2, float t, float* result)
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;
1499
static void SetGLVertex(const float* v, float rad)
1501
float Len = sqrtf(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
1503
if (Len >= 1.0e-10f)
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);
1513
static void SphereSegment(
1514
const float* v1, const float* v2, const float* v3, float r, int Levels)
1518
for (i = 0; i < Levels; i++)
1520
float A[3], B[3], C[3], D[3];
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);
1527
glBegin(GL_TRIANGLE_STRIP);
1532
for (j = 1; j <= i; j++)
1536
InterpolateVertex(B, A, (float) j / (i + 1), v);
1539
InterpolateVertex(C, D, (float) j / i, v);
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. */
1555
static void DrawSphere(float rad)
1558
float v1[3], v2[3], v3[3];
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);
1566
SphereSegment(v2, v1, v3, rad, Levels);
1568
v1[0] = v3[2] = -1.0f;
1569
SphereSegment(v2, v1, v3, rad, Levels);
1572
SphereSegment(v1, v2, v3, rad, Levels);
1576
static void DrawRing(void)
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);
1584
gluCylinder(pQuad, 1.0f, 1.0f, w, Facets, 1);
1585
gluQuadricOrientation(pQuad, GLU_INSIDE);
1587
gluCylinder(pQuad, 0.7f, 0.7f, w, Facets, 1);
1588
gluQuadricOrientation(pQuad, GLU_OUTSIDE);
1590
glTranslatef(0.0f, 0.0f, w);
1591
gluDisk(pQuad, 0.7, 1.0f, Facets, 1);
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);
1597
gluDeleteQuadric(pQuad);
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. */
1605
static void DrawClub(void)
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};
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]));
1619
for (i = 0; i < n; i += 2)
1621
float a1 = i * PI * 2.0f / n;
1622
float a2 = (i + 1) * PI * 2.0f / n;
1624
glBegin(GL_TRIANGLE_STRIP);
1625
for (j = 1; j < 4; j++)
1627
glNormal3f(cosf(na[j]) * cosf(a1),
1628
cosf(na[j]) * sinf(a1), sinf(na[j]));
1630
glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1632
glNormal3f(cosf(na[j]) * cosf(a2),
1633
cosf(na[j]) * sinf(a2), sinf(na[j]));
1635
glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1640
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, HandleCol);
1642
for (i = 1; i < n; i += 2)
1644
float a1 = i * PI * 2.0f / n;
1645
float a2 = (i + 1) * PI * 2.0f / n;
1647
glBegin(GL_TRIANGLE_STRIP);
1648
for (j = 1; j < 4; j++)
1650
glNormal3f(cosf(na[j]) * cosf(a1),
1651
cosf(na[j]) * sinf(a1), sinf(na[j]));
1653
glVertex3f(r[j] * cosf(a1), r[j] * sinf(a1), z[j]);
1655
glNormal3f(cosf(na[j]) * cosf(a2),
1656
cosf(na[j]) * sinf(a2), sinf(na[j]));
1658
glVertex3f(r[j] * cosf(a2), r[j] * sinf(a2), z[j]);
1663
pQuad = gluNewQuadric();
1664
glTranslatef(0.0f, 0.0f, z[0]);
1665
gluCylinder(pQuad, r[0], r[1], z[1] - z[0], n, 1);
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);
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. */
1680
static int InitGLDisplayLists(void)
1682
int s = glGenLists(6);
1685
glNewList(s + DL_BALL, GL_COMPILE);
1686
DrawSphere(BallRad);
1689
glNewList(s + DL_CLUB, GL_COMPILE);
1693
glNewList(s + DL_RING, GL_COMPILE);
1697
pQuad = gluNewQuadric();
1698
gluQuadricNormals(pQuad, GLU_SMOOTH);
1700
glNewList(s + DL_TORSO, GL_COMPILE);
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);
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);
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);
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);
1727
glTranslatef(0.0f, 0.0f, .3f);
1728
gluDisk(pQuad, 0.0, 0.5, 15, 1);
1732
glNewList(s + DL_UPPERARM, GL_COMPILE);
1733
gluQuadricNormals(pQuad, GLU_SMOOTH);
1734
gluQuadricDrawStyle(pQuad, GLU_FILL);
1735
gluSphere(pQuad, 0.3, 12, 8);
1737
gluCylinder(pQuad, 0.3, 0.3, UArmLen, 12, 1);
1738
glTranslatef(0.0f, 0.0f, UArmLen);
1739
gluSphere(pQuad, 0.3, 12, 8);
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);
1748
gluDeleteQuadric(pQuad);
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
1763
static void DrawArm(RENDER_STATE* pState, float TimePos, int Left)
1766
float x, y, len, len2, ang, ang2;
1768
GetHandPosition(pState->pPattern, Left, TimePos, &Pos);
1770
x = Pos.x + (Left ? -ShoulderPos[0] : ShoulderPos[0]);
1771
y = Pos.y - ShoulderPos[1];
1774
len = sqrtf(x * x + y * y + ShoulderPos[2] * ShoulderPos[2]);
1775
len2 = sqrtf(x * x + ShoulderPos[2] * ShoulderPos[2]);
1777
ang = (float) CosineRule(UArmLen, len, LArmLen);
1778
ang2 = (float) CosineRule(UArmLen, LArmLen, len);
1780
if (ang == 0.0 && ang2 == 0)
1785
glTranslatef(Left ? ShoulderPos[0] : -ShoulderPos[0], ShoulderPos[1],
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);
1793
glRotatef((float)(ang2 - 180.0), 1.0f, 0.0f, 0.f);
1794
glCallList(DL_FOREARM + pState->DLStart);
1799
static void DrawGLScene(RENDER_STATE* pState)
1801
float Time = pState->Time;
1802
int nCols = sizeof(Cols) / sizeof(Cols[0]);
1805
PATTERN_INFO* pPattern = pState->pPattern;
1807
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
1809
glMatrixMode(GL_MODELVIEW);
1811
glTranslatef(5.0f * sinf(pState->TranslateAngle), 0.0f, 0.0f);
1813
gltrackball_rotate (pState->trackball);
1815
glRotatef(pState->SpinAngle, 0.0f, 1.0f, 0.0f);
1816
glTranslatef(0.0, 0.0, -1.0f);
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);
1822
for (i = 0; i < pPattern->Objects; i++)
1826
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, Cols[i % nCols]);
1829
switch (pPattern->pObjectInfo[i].ObjectType)
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);
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);
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);
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);
1870
static int RandInRange(int Min, int Max)
1872
return Min + random() % (1 + Max - Min);
1876
static void UpdatePattern(
1877
RENDER_STATE* pState, int MinBalls, int MaxBalls,
1878
int MinHeightInc, int MaxHeightInc)
1880
if (pState->pPattern != NULL)
1881
ReleasePatternInfo(pState->pPattern);
1883
pState->pPattern = (PATTERN_INFO*) malloc(sizeof(PATTERN_INFO));
1885
if ((random() % 3) == 1)
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);
1896
int ballcount, maxweight;
1897
const int RandPatternLen = 1500;
1899
ballcount = RandInRange(MinBalls, MaxBalls);
1900
maxweight = ballcount + RandInRange(MinHeightInc, MaxHeightInc);
1902
pRand = Generate(RandPatternLen, maxweight, ballcount);
1903
InitPatternInfo(pState->pPattern, pRand, NULL, RandPatternLen);
1907
pState->CameraElev = 50.0f - random() % 90;
1908
pState->TranslateAngle = random() % 360;
1909
pState->SpinAngle = random() % 360;
1910
pState->Time = 50.0f;
1915
/*******************************************************************************
1917
* XScreenSaver Configuration
1919
******************************************************************************/
1923
GLXContext* glxContext;
1924
RENDER_STATE RenderState;
1925
float CurrentFrameRate;
1926
unsigned FramesSinceSync;
1927
unsigned LastSyncTime;
1931
#define DEF_MAX_OBJS "8"
1932
#define DEF_MIN_OBJS "3"
1933
#define DEF_MAX_HINC "6"
1934
#define DEF_MIN_HINC "2"
1935
#define DEF_JUGGLE_SPEED "2.2"
1936
#define DEF_TRANSLATE_SPEED "0.1"
1937
#define DEF_SPIN_SPEED "20.0"
1939
static JUGGLER3D_CONFIG* pConfigInfo = NULL;
1940
static int MaxObjects;
1941
static int MinObjects;
1942
static int MaxHeightInc;
1943
static int MinHeightInc;
1944
static float SpinSpeed;
1945
static float TranslateSpeed;
1946
static float JuggleSpeed;
1948
static XrmOptionDescRec opts[] =
1950
{"-spin", ".spinSpeed", XrmoptionSepArg, 0},
1951
{"-trans", ".translateSpeed", XrmoptionSepArg, 0},
1952
{"-speed", ".juggleSpeed", XrmoptionSepArg, 0},
1953
{"-maxobjs", ".maxObjs", XrmoptionSepArg, 0},
1954
{"-minobjs", ".minObjs", XrmoptionSepArg, 0},
1955
{"-maxhinc", ".maxHinc", XrmoptionSepArg, 0},
1956
{"-minhinc", ".minHinc", XrmoptionSepArg, 0},
1960
static argtype vars[] =
1962
{&MaxObjects, "maxObjs", "MaxObjs", DEF_MAX_OBJS, t_Int},
1963
{&MinObjects, "minObjs", "MinObjs", DEF_MIN_OBJS, t_Int},
1964
{&MaxHeightInc, "maxHinc", "MaxHinc", DEF_MAX_HINC, t_Int},
1965
{&MinHeightInc, "minHinc", "MinHinc", DEF_MIN_HINC, t_Int},
1966
{&JuggleSpeed, "juggleSpeed", "JuggleSpeed", DEF_JUGGLE_SPEED, t_Float},
1967
{&TranslateSpeed, "translateSpeed", "TranslateSpeed", DEF_TRANSLATE_SPEED, t_Float},
1968
{&SpinSpeed, "spinSpeed", "SpinSpeed", DEF_SPIN_SPEED, t_Float},
1972
ENTRYPOINT ModeSpecOpt juggler3d_opts = {countof(opts), opts, countof(vars), vars};
1975
ENTRYPOINT void reshape_juggler3d(ModeInfo *mi, int width, int height)
1977
JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
1978
ResizeGL(&pConfig->RenderState, width, height);
1982
ENTRYPOINT void init_juggler3d(ModeInfo* mi)
1984
JUGGLER3D_CONFIG* pConfig;
1986
if (pConfigInfo == NULL)
1988
/* Apply suitable bounds checks to the input parameters */
1989
MaxObjects = max(3, min(MaxObjects, 36));
1990
MinObjects = max(3, min(MinObjects, MaxObjects));
1992
MaxHeightInc = max(1, min(MaxHeightInc, 32));
1993
MinHeightInc = max(1, min(MinHeightInc, MaxHeightInc));
1995
pConfigInfo = (JUGGLER3D_CONFIG*) calloc(
1996
MI_NUM_SCREENS(mi), sizeof(JUGGLER3D_CONFIG));
1997
if (pConfigInfo == NULL)
1999
fprintf(stderr, "%s: out of memory\n", progname);
2004
pConfig = &pConfigInfo[MI_SCREEN(mi)];
2005
pConfig->glxContext = init_GL(mi);
2006
pConfig->CurrentFrameRate = 0.0f;
2007
pConfig->FramesSinceSync = 0;
2008
pConfig->LastSyncTime = 0;
2009
InitGLSettings(&pConfig->RenderState, MI_IS_WIREFRAME(mi));
2011
UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2012
MinHeightInc, MaxHeightInc);
2014
reshape_juggler3d(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2018
ENTRYPOINT void draw_juggler3d(ModeInfo* mi)
2020
JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2021
Display* pDisplay = MI_DISPLAY(mi);
2022
Window hwnd = MI_WINDOW(mi);
2024
if (pConfig->glxContext == NULL)
2027
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(pConfig->glxContext));
2029
/* While drawing, keep track of the rendering speed so we can adjust the
2030
* animation speed so things appear consistent. The basis of the this
2031
* code comes from the frame rate counter (fps.c) but has been modified
2032
* so that it reports the initial frame rate earlier (after 0.02 secs
2033
* instead of 1 sec). */
2035
if (pConfig->FramesSinceSync >= 1 * (int) pConfig->CurrentFrameRate)
2037
struct timeval tvnow;
2040
# ifdef GETTIMEOFDAY_TWO_ARGS
2041
struct timezone tzp;
2042
gettimeofday(&tvnow, &tzp);
2044
gettimeofday(&tvnow);
2047
now = (unsigned) (tvnow.tv_sec * 1000000 + tvnow.tv_usec);
2048
if (pConfig->FramesSinceSync == 0)
2050
pConfig->LastSyncTime = now;
2054
unsigned Delta = now - pConfig->LastSyncTime;
2057
pConfig->LastSyncTime = now;
2058
pConfig->CurrentFrameRate =
2059
(pConfig->FramesSinceSync * 1.0e6f) / Delta;
2060
pConfig->FramesSinceSync = 0;
2065
pConfig->FramesSinceSync++;
2067
if (pConfig->RenderState.Time > 150.0f)
2069
UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2070
MinHeightInc, MaxHeightInc);
2072
DrawGLScene(&pConfig->RenderState);
2074
if (pConfig->CurrentFrameRate > 1.0e-6f)
2076
pConfig->RenderState.Time += JuggleSpeed / pConfig->CurrentFrameRate;
2077
pConfig->RenderState.SpinAngle += SpinSpeed / pConfig->CurrentFrameRate;
2078
pConfig->RenderState.TranslateAngle +=
2079
TranslateSpeed / pConfig->CurrentFrameRate;
2086
glXSwapBuffers(pDisplay, hwnd);
2090
ENTRYPOINT Bool juggler3d_handle_event(ModeInfo* mi, XEvent* pEvent)
2092
JUGGLER3D_CONFIG* pConfig = &pConfigInfo[MI_SCREEN(mi)];
2093
RENDER_STATE* pState = &pConfig->RenderState;
2095
if (pEvent->xany.type == ButtonPress &&
2096
pEvent->xbutton.button == Button1)
2098
pState->button_down_p = True;
2099
gltrackball_start (pState->trackball,
2100
pEvent->xbutton.x, pEvent->xbutton.y,
2101
MI_WIDTH (mi), MI_HEIGHT (mi));
2104
else if (pEvent->xany.type == ButtonRelease &&
2105
pEvent->xbutton.button == Button1)
2107
pState->button_down_p = False;
2110
else if (pEvent->xany.type == ButtonPress &&
2111
(pEvent->xbutton.button == Button4 ||
2112
pEvent->xbutton.button == Button5 ||
2113
pEvent->xbutton.button == Button6 ||
2114
pEvent->xbutton.button == Button7))
2116
gltrackball_mousewheel (pState->trackball, pEvent->xbutton.button, 2,
2117
!pEvent->xbutton.state);
2120
else if (pEvent->xany.type == MotionNotify &&
2121
pState->button_down_p)
2123
gltrackball_track (pState->trackball,
2124
pEvent->xmotion.x, pEvent->xmotion.y,
2125
MI_WIDTH (mi), MI_HEIGHT (mi));
2128
else if (pEvent->xany.type == KeyPress)
2132
int count = XLookupString(&pEvent->xkey, str, 20, &Key, 0);
2136
UpdatePattern(&pConfig->RenderState, MinObjects, MaxObjects,
2137
MinHeightInc, MaxHeightInc);
2144
XSCREENSAVER_MODULE ("Juggler3D", juggler3d)
152
#define DEF_PATTERN "random" /* All patterns */
153
#define DEF_TAIL "1" /* No trace */
155
/* Maybe a ROLA BOLA would be at a better angle for viewing */
156
#define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
158
#define DEF_REAL "True"
159
#define DEF_DESCRIBE "True"
161
#define DEF_BALLS "True" /* Use Balls */
162
#define DEF_CLUBS "True" /* Use Clubs */
163
#define DEF_TORCHES "True" /* Use Torches */
164
#define DEF_KNIVES "True" /* Use Knives */
165
#define DEF_RINGS "True" /* Use Rings */
166
#define DEF_BBALLS "True" /* Use Bowling Balls */
168
static char *pattern;
174
static Bool describe;
183
static XrmOptionDescRec opts[] = {
184
{"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
185
{"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
187
{"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
188
{"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
190
{"-real", ".juggle.real", XrmoptionNoArg, "on" },
191
{"+real", ".juggle.real", XrmoptionNoArg, "off" },
192
{"-describe", ".juggle.describe", XrmoptionNoArg, "on" },
193
{"+describe", ".juggle.describe", XrmoptionNoArg, "off" },
194
{"-balls", ".juggle.balls", XrmoptionNoArg, "on" },
195
{"+balls", ".juggle.balls", XrmoptionNoArg, "off" },
196
{"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" },
197
{"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" },
198
{"-torches", ".juggle.torches", XrmoptionNoArg, "on" },
199
{"+torches", ".juggle.torches", XrmoptionNoArg, "off" },
200
{"-knives", ".juggle.knives", XrmoptionNoArg, "on" },
201
{"+knives", ".juggle.knives", XrmoptionNoArg, "off" },
202
{"-rings", ".juggle.rings", XrmoptionNoArg, "on" },
203
{"+rings", ".juggle.rings", XrmoptionNoArg, "off" },
204
{"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" },
205
{"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" },
206
{"-only", ".juggle.only", XrmoptionSepArg, NULL },
209
static argtype vars[] = {
210
{ &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
211
{ &tail, "tail", "Tail", DEF_TAIL, t_Int },
213
{ &uni, "uni", "Uni", DEF_UNI, t_Bool },
215
{ &real, "real", "Real", DEF_REAL, t_Bool },
216
{ &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool },
217
{ &balls, "balls", "Clubs", DEF_BALLS, t_Bool },
218
{ &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool },
219
{ &torches, "torches", "Torches", DEF_TORCHES, t_Bool },
220
{ &knives, "knives", "Knives", DEF_KNIVES, t_Bool },
221
{ &rings, "rings", "Rings", DEF_RINGS, t_Bool },
222
{ &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool },
223
{ &only, "only", "BBalls", " ", t_String },
226
static OptionStruct desc[] =
228
{ "-pattern string", "Cambridge Juggling Pattern" },
229
{ "-tail num", "Trace Juggling Patterns" },
231
{ "-/+uni", "Unicycle" },
233
{ "-/+real", "Real-time" },
234
{ "-/+describe", "turn on/off pattern descriptions." },
235
{ "-/+balls", "turn on/off Balls." },
236
{ "-/+clubs", "turn on/off Clubs." },
237
{ "-/+torches", "turn on/off Flaming Torches." },
238
{ "-/+knives", "turn on/off Knives." },
239
{ "-/+rings", "turn on/off Rings." },
240
{ "-/+bballs", "turn on/off Bowling Balls." },
241
{ "-only", "Turn off all objects but the named one." },
244
ENTRYPOINT ModeSpecOpt juggle_opts =
245
{countof(opts), opts, countof(vars), vars, desc};
248
/* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All
249
"thicknesses" are scaled by sqrt(sp->scale) so that they are
250
proportionally thicker for smaller windows. Objects spinning out
251
of the plane (such as clubs) fake perspective by compressing their
252
horizontal coordinates by PERSPEC */
256
#define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
258
#define BALLRADIUS ARMWIDTH
260
/* build all the models assuming a 480px high scene */
261
#define SCENE_HEIGHT 480
262
#define SCENE_WIDTH ((int)(SCENE_HEIGHT*(MI_WIDTH(mi)/(float)MI_HEIGHT(mi))))
264
/*#define PERSPEC 0.4*/
267
#define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
269
/* Timing based on count. Units are milliseconds. Juggles per second
270
is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
272
#define THROW_CATCH_INTERVAL (sp->count)
273
#define THROW_NULL_INTERVAL (sp->count * 0.5)
274
#define CATCH_THROW_INTERVAL (sp->count * 0.2)
276
/********************************************************************
277
* Trace Definitions *
279
* These record rendering data so that a drawn object can be erased *
280
* later. Each object has its own Trace list. *
282
********************************************************************/
284
typedef struct {double x, y; } DXPoint;
285
typedef struct trace *TracePtr;
286
typedef struct trace {
297
/*******************************************************************
298
* Object Definitions *
300
* These describe the various types of Object that can be juggled *
302
*******************************************************************/
303
typedef int (DrawProc)(ModeInfo*, unsigned long, Trace *);
305
static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
306
static DrawProc show_ring, show_bball;
308
typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
309
NUM_OBJECT_TYPES} ObjType;
311
#define OBJMIXPROB 20 /* inverse of the chances of using an odd
312
object in the pattern */
314
static const GLfloat body_color_1[4] = { 0.9, 0.7, 0.5, 1 };
315
static const GLfloat body_color_2[4] = { 0.6, 0.4, 0.2, 1 };
317
static const struct {
318
DrawProc *draw; /* Object Rendering function */
319
int handle; /* Length of object's handle */
320
int mintrail; /* Minimum trail length */
321
double cor; /* Coefficient of Restitution. perfect bounce = 1 */
322
double weight; /* Heavier objects don't get thrown as high */
335
0.55, /* Clubs don't bounce too well */
341
20, /* Torches need flames */
342
0, /* Torches don't bounce -- fire risk! */
349
0, /* Knives don't bounce */
368
/**************************
369
* Trajectory definitions *
370
**************************/
372
typedef enum {HEIGHT, ADAM} Notation;
373
typedef enum {Empty, Full, Ball} Throwable;
374
typedef enum {LEFT, RIGHT} Hand;
375
typedef enum {THROW, CATCH} Action;
376
typedef enum {HAND, ELBOW, SHOULDER} Joint;
377
typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
378
PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
379
typedef struct {double a, b, c, d; } Spline;
380
typedef DXPoint Arm[3];
383
/* Object is an arbitrary object being juggled. Each Trajectory
384
* references an Object ("count" tracks this), and each Object is also
385
* linked into a global Objects list. Objects may include a Trace
386
* list for tracking erasures. */
387
typedef struct object *ObjectPtr;
388
typedef struct object {
389
ObjectPtr next, prev;
393
int count; /* reference count */
394
Bool active; /* Object is in use */
404
/* Trajectory is a segment of juggling action. A list of Trajectories
405
* defines the juggling performance. The Trajectory list goes through
406
* multiple processing steps to convert it from basic juggling
407
* notation into rendering data. */
409
typedef struct trajectory *TrajectoryPtr;
410
typedef struct trajectory {
411
TrajectoryPtr prev, next; /* for building list */
412
TrajectoryStatus status;
430
TrajectoryPtr balllink;
431
TrajectoryPtr handlink;
434
double cx; /* Moving juggler */
435
double x, y; /* current position */
436
double dx, dy; /* initial velocity */
440
unsigned long start, finish;
454
const char * pattern;
458
/* List of popular patterns, in any order */
459
/* Patterns should be given in Adam notation so the generator can
460
concatenate them safely. Null descriptions are ok. Height
461
notation will be displayed automatically. */
462
/* Can't const this because it is qsorted. This *should* be reentrant,
464
static /*const*/ patternstruct portfolio[] = {
465
{"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
466
{"[2 0]", /* 4 0 */ "2 in 1 hand"},
467
{"[2 0 1]", /* 5 0 1 */},
468
{"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
469
{"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
470
{"[2 0 1 1]", /* 6 0 1 1 */},
472
{"[3]", /* 3 */ "3 cascade"},
473
{"[+3]", /* +3 */ "reverse 3 cascade"},
474
{"[=3]", /* =3 */ "cascade 3 under arm"},
475
{"[&3]", /* &3 */ "cascade 3 catching under arm"},
476
{"[_3]", /* _3 */ "bouncing 3 cascade"},
477
{"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
478
{"[3 2 1]", /* 5 3 1" */},
479
{"[3 3 1]", /* 4 4 1" */},
480
{"[3 1 2]", /* 6 1 2 */ "See-saw"},
481
{"[=3 3 1 2]", /* =4 5 1 2 */},
482
{"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
483
{"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
484
{"[3 3 1]", /* 4 4 1 */},
485
{"[+3 2 3]", /* +4 2 3 */},
486
{"[+3 1]", /* +5 1 */ "3 shower"},
487
{"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
488
{"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
489
{"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
490
{"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
491
{"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
492
{"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
493
{"[3 2 2 0 3 2 0 2 3 0 2 2 0]", /* 7 3 3 0 7 3 0 3 7 0 3 3 0 */},
494
{"[3 0 2 0]", /* 8 0 4 0 */},
495
{"[_3 2 1]", /* _5 3 1 */},
496
{"[_3 0 1]", /* _8 0 1 */},
497
{"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
498
{"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
500
{"[4]", /* 4 */ "4 cascade"},
501
{"[+4 3]", /* +5 3 */ "4 ball half shower"},
502
{"[4 4 2]", /* 5 5 2 */},
503
{"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
504
{"[+4 3 +4]", /* +5 3 +4 */},
505
{"[4 3 4 4]", /* 5 3 4 4 */},
506
{"[4 3 3 4]", /* 6 3 3 4 */},
507
{"[4 3 2 4", /* 6 4 2 4 */},
508
{"[+4 1]", /* +7 1 */ "4 shower"},
509
{"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
510
{"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
511
{"[+4 2 1 3]", /* +9 3 1 3 */},
512
{"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
513
{"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
514
{"[_4 3 3]", /* _6 3 3 */},
515
{"[_4 3 1]", /* _7 4 1 */},
516
{"[_4 2 1]", /* _8 3 1 */},
517
{"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
518
{"[_4 1 3 1]", /* _9 1 5 1 */},
519
{"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
521
{"[5]", /* 5 */ "5 cascade"},
522
{"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
523
{"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
524
{"[5 4 4]", /* 7 4 4 */},
525
{"[_5 4 4]", /* _7 4 4 */},
526
{"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
527
{"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
528
{"[_5 4 1 +4]", /* _9 5 1 5 */},
529
{"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
530
{"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
532
{"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
534
{"[6]", /* 6 */ "6 cascade"},
535
{"[+6 5]", /* +7 5 */},
536
{"[6 4]", /* 8 4 */},
537
{"[+6 3]", /* +9 3 */},
538
{"[6 5 4 4]", /* 9 7 4 4 */},
539
{"[+6 5 5 5]", /* +9 5 5 5 */},
540
{"[6 0 6]", /* 9 0 9 */},
541
{"[_6 0 _6]", /* _9 0 _9 */},
543
{"[_7]", /* _7 */ "bouncing 7 cascade"},
544
{"[7]", /* 7 */ "7 cascade"},
545
{"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
551
typedef struct { int start; int number; } PatternIndex;
553
struct patternindex {
556
PatternIndex index[countof(portfolio)];
560
/* Jugglestruct: per-screen global data. The master Object
561
* and Trajectory lists are anchored here. */
563
GLXContext *glx_context;
565
trackball_state *trackball;
576
time_t begintime; /* should make 'time' usable for at least 48 days
577
on a 32-bit machine */
578
unsigned long time; /* millisecond timer*/
581
struct patternindex patternindex;
583
XFontStruct *mode_font;
587
static jugglestruct *juggles = (jugglestruct *) NULL;
593
#define DUP_OBJECT(n, t) { \
594
(n)->object = (t)->object; \
595
if((n)->object != NULL) (n)->object->count++; \
598
/* t must point to an existing element. t must not be an
599
expression ending ->next or ->prev */
600
#define REMOVE(t) { \
601
(t)->next->prev = (t)->prev; \
602
(t)->prev->next = (t)->next; \
606
/* t receives element to be created and added to the list. ot must
607
point to an existing element or be identical to t to start a new
608
list. Applicable to Trajectories, Objects and Traces. */
609
#define ADD_ELEMENT(type, t, ot) \
610
if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
611
(t)->next = (ot)->next; \
614
(t)->next->prev = (t); \
618
object_destroy(Object* o)
620
if(o->trace != NULL) {
621
while(o->trace->next != o->trace) {
622
Trace *s = o->trace->next;
623
REMOVE(s); /* Don't eliminate 's' */
631
trajectory_destroy(Trajectory *t) {
632
if(t->name != NULL) free(t->name);
633
if(t->pattern != NULL) free(t->pattern);
634
/* Reduce object link count and call destructor if necessary */
635
if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
636
object_destroy(t->object);
638
REMOVE(t); /* Unlink and free */
642
free_juggle(jugglestruct *sp) {
643
if (sp->head != NULL) {
644
while (sp->head->next != sp->head) {
645
trajectory_destroy(sp->head->next);
648
sp->head = (Trajectory *) NULL;
650
if(sp->objects != NULL) {
651
while (sp->objects->next != sp->objects) {
652
object_destroy(sp->objects->next);
655
sp->objects = (Object*)NULL;
657
if(sp->pattern != NULL) {
661
if (sp->mode_font!=None) {
662
XFreeFontInfo(NULL,sp->mode_font,1);
663
sp->mode_font = None;
668
add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
672
ADD_ELEMENT(Trajectory, t, sp->head->prev);
673
if(t == NULL){ /* Out of Memory */
679
t->name = strdup(name);
692
/* add a Thratch to the performance */
694
program(ModeInfo *mi, const char *patn, const char *name, int cycles)
696
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
702
if (MI_IS_VERBOSE(mi)) {
703
(void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
704
MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
707
for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
708
have been programmed */
709
/* title is the pattern name to be supplied to the first throw of
710
a sequence. If no name if given, use an empty title so that
711
the sequences are still delimited. */
712
const char *title = (name != NULL)? name : "";
717
for(p=patn; *p; p++) {
718
if (*p >= '0' && *p <='9') {
720
h = 10*h + (*p - '0');
722
Notation nn = notation;
724
case '[': /* begin Adam notation */
727
case '-': /* Inside throw */
730
case '+': /* Outside throw */
731
case '=': /* Cross throw */
732
case '&': /* Cross catch */
733
case 'x': /* Cross throw and catch */
734
case '_': /* Bounce */
735
case 'k': /* Kickup */
738
case '*': /* Lose ball */
742
case ']': /* end Adam notation */
748
if (!add_throw(sp, type, h, notation, title))
758
if(w == 0) { /* Only warn on first pass */
759
(void) fprintf(stderr,
760
"juggle[%d]: Unexpected pattern instruction: '%c'\n",
767
if (seen) { /* end of sequence */
768
if (!add_throw(sp, type, h, notation, title))
782
[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
784
4 4 1 3 12 2 4 1 4 4 13 0 3 1
787
#define BOUNCEOVER 10
791
/* Convert Adam notation into heights */
793
adam(jugglestruct *sp)
796
for(t = sp->head->next; t != sp->head; t = t->next) {
797
if (t->status == ATCH) {
800
for(p = t->next; a > 0; p = p->next) {
802
t->height = -9; /* Indicate end of processing for name() */
805
if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
810
if(t->height > BOUNCEOVER && t->posn == ' '){
811
t->posn = '_'; /* high defaults can be bounced */
812
} else if(t->height < 3 && t->posn == '_') {
813
t->posn = ' '; /* Can't bounce short throws. */
815
if(t->height < KICKMIN && t->posn == 'k'){
816
t->posn = ' '; /* Can't kick short throws */
818
if(t->height > THROWMAX){
819
t->posn = 'k'; /* Use kicks for ridiculously high throws */
826
/* Discover converted heights and update the sequence title */
828
name(jugglestruct *sp)
833
for(t = sp->head->next; t != sp->head; t = t->next) {
834
if (t->status == THRATCH && t->name != NULL) {
836
for(p = t; p == t || p->name == NULL; p = p->next) {
837
if(p == sp->head || p->height < 0) { /* end of reliable data */
841
b += sprintf(b, " %d", p->height);
843
b += sprintf(b, " %c%d", p->posn, p->height);
845
if(b - buffer > 500) break; /* otherwise this could eventually
846
overflow. It'll be too big to
850
(void) sprintf(b, ", %s", t->name);
852
free(t->name); /* Don't need name any more, it's been converted
855
if(t->pattern != NULL) free(t->pattern);
856
t->pattern = strdup(buffer);
861
/* Split Thratch notation into explicit throws and catches.
862
Usually Catch follows Throw in same hand, but take care of special
865
/* ..n1.. -> .. LTn RT1 LC RC .. */
866
/* ..nm.. -> .. LTn LC RTm RC .. */
869
part(jugglestruct *sp)
871
Trajectory *t, *nt, *p;
872
Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
874
for (t = sp->head->next; t != sp->head; t = t->next) {
875
if (t->status > THRATCH) {
877
} else if (t->status == THRATCH) {
880
/* plausibility check */
881
if (t->height <= 2 && t->posn == '_') {
882
t->posn = ' '; /* no short bounces */
884
if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
885
t->posn = ' '; /* 1's need close catches */
890
case ' ': posn = '-'; t->posn = '+'; break;
891
case '+': posn = '+'; t->posn = '-'; break;
892
case '=': posn = '='; t->posn = '+'; break;
893
case '&': posn = '+'; t->posn = '='; break;
894
case 'x': posn = '='; t->posn = '='; break;
895
case '_': posn = '_'; t->posn = '-'; break;
896
case 'k': posn = 'k'; t->posn = 'k'; break;
898
(void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
901
hand = (Hand) ((hand + 1) % 2);
906
if (t->height == 1 && p != sp->head) {
907
p = p->prev; /* '1's are thrown earlier than usual */
913
ADD_ELEMENT(Trajectory, nt, p);
921
nt->height = t->height;
931
choose_object(void) {
934
o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
935
if(balls && o == BALL) break;
936
if(clubs && o == CLUB) break;
937
if(torches && o == TORCH) break;
938
if(knives && o == KNIFE) break;
939
if(rings && o == RING) break;
940
if(bballs && o == BBALLS) break;
945
/* Connnect up throws and catches to figure out which ball goes where.
946
Do the same with the juggler's hands. */
951
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
954
for (t = sp->head->next; t != sp->head; t = t->next) {
955
if (t->status == ACTION) {
956
if (t->action == THROW) {
957
if (t->type == Empty) {
958
/* Create new Object */
959
ADD_ELEMENT(Object, t->object, sp->objects);
960
t->object->count = 1;
961
t->object->tracelen = 0;
962
t->object->active = False;
963
/* Initialise object's circular trace list */
964
ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
966
if (MI_NPIXELS(mi) > 2) {
967
t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
970
t->object->color = 1;
972
t->object->color = 0;
976
/* Small chance of picking a random object instead of the
978
if(NRAND(OBJMIXPROB) == 0) {
979
t->object->type = choose_object();
981
t->object->type = sp->objtypes;
984
/* Check to see if we need trails for this object */
985
if(tail < ObjectDefs[t->object->type].mintrail) {
986
t->object->tail = ObjectDefs[t->object->type].mintrail;
988
t->object->tail = tail;
992
/* Balls can change divisions at each throw */
993
/* no, that looks stupid. -jwz */
994
if (t->divisions < 1)
995
t->divisions = 2 * (NRAND(2) + 1);
997
/* search forward for next catch in this hand */
998
for (p = t->next; t->handlink == NULL; p = p->next) {
999
if(p->status < ACTION || p == sp->head) return;
1000
if (p->action == CATCH) {
1001
if (t->handlink == NULL && p->hand == t->hand) {
1007
if (t->height > 0) {
1010
/* search forward for next ball catch */
1011
for (p = t->next; t->balllink == NULL; p = p->next) {
1012
if(p->status < ACTION || p == sp->head) {
1016
if (p->action == CATCH) {
1017
if (t->balllink == NULL && --h < 1) { /* caught */
1018
t->balllink = p; /* complete trajectory */
1020
if (p->type == Full) {
1021
(void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1022
MI_SCREEN(mi), t->object->color);
1026
DUP_OBJECT(p, t); /* accept catch */
1027
p->angle = t->angle;
1028
p->divisions = t->divisions;
1033
t->type = Empty; /* thrown */
1034
} else if (t->action == CATCH) {
1035
/* search forward for next throw from this hand */
1036
for (p = t->next; t->handlink == NULL; p = p->next) {
1037
if(p->status < ACTION || p == sp->head) return;
1038
if (p->action == THROW && p->hand == t->hand) {
1039
p->type = t->type; /* pass ball */
1040
DUP_OBJECT(p, t); /* pass object */
1041
p->divisions = t->divisions;
1046
t->status = LINKEDACTION;
1051
/* Clap when both hands are empty */
1053
clap(jugglestruct *sp)
1056
for (t = sp->head->next; t != sp->head; t = t->next) {
1057
if (t->status == LINKEDACTION &&
1058
t->action == CATCH &&
1060
t->handlink != NULL &&
1061
t->handlink->height == 0) { /* Completely idle hand */
1063
for (p = t->next; p != sp->head; p = p->next) {
1064
if (p->status == LINKEDACTION &&
1065
p->action == CATCH &&
1066
p->hand != t->hand) { /* Next catch other hand */
1067
if(p->type == Empty &&
1068
p->handlink != NULL &&
1069
p->handlink->height == 0) { /* Also completely idle */
1071
t->handlink->posn = '^'; /* Move first hand's empty throw */
1072
p->posn = '^'; /* to meet second hand's empty
1076
break; /* Only need first catch */
1083
#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1085
/* Compute single spline from x0 with velocity dx0 at time t0 to x1
1086
with velocity dx1 at time t1 */
1088
makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1097
a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1098
b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1103
s.c = (3*a*t0 - 2*b)*t0 + c;
1104
s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1108
/* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1109
time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1110
velocity dx2 at time t2. The arrival and departure velocities at
1111
x1, t1 must be the same. */
1113
makeSplinePair(Spline *s1, Spline *s2,
1114
double x0, double dx0, int t0,
1116
double x2, double dx2, int t2)
1118
double x10, x21, t21, t10, t20, dx1;
1124
dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1125
- dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1127
*s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1128
*s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1132
/* Compute a Ballistic path in a pair of degenerate splines. sx goes
1133
from x at time t at constant velocity dx. sy goes from y at time t
1134
with velocity dy and constant acceleration g. */
1136
makeParabola(Trajectory *n,
1137
double x, double dx, double y, double dy, double g)
1139
double t = (double)n->start;
1143
n->xp.d = -dx*t + x;
1146
n->yp.c = -g*t + dy;
1147
n->yp.d = g/2*t*t - dy*t + y;
1153
#define SX 25 /* Shoulder Width */
1155
/* Convert hand position symbols into actual time/space coordinates */
1157
positions(jugglestruct *sp)
1160
unsigned long now = sp->time; /* Make sure we're not lost in the past */
1161
for (t = sp->head->next; t != sp->head; t = t->next) {
1162
if (t->status >= PTHRATCH) {
1164
} else if (t->status == ACTION || t->status == LINKEDACTION) {
1165
/* Allow ACTIONs to be annotated, but we won't mark them ready
1166
for the next stage */
1173
if (t->action == CATCH) { /* Throw-to-catch */
1174
if (t->type == Empty) {
1175
now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1176
} else { /* successful catch */
1177
now += (int)(THROW_CATCH_INTERVAL);
1179
} else { /* Catch-to-throw */
1180
if(t->object != NULL) {
1181
now += (int) (CATCH_THROW_INTERVAL *
1182
ObjectDefs[t->object->type].weight);
1184
now += (int) (CATCH_THROW_INTERVAL);
1190
else /* Concatenated performances may need clock resync */
1198
/* Add room for the handle */
1199
if(t->action == CATCH && t->object != NULL)
1200
yo -= ObjectDefs[t->object->type].handle;
1203
case '-': xo = sx - pose; break;
1206
case '+': xo = sx + pose; break;
1208
case '=': xo = - sx - pose; yo += pose; break;
1209
case '^': xo = 0; yo += pose*2; break; /* clap */
1211
(void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1215
#ifdef _2DSpinsDontWorkIn3D
1216
t->angle = (((t->hand == LEFT) ^
1217
(t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1223
t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1226
/* Only mark complete if it was already linked */
1227
if(t->status == LINKEDACTION) {
1228
t->status = PTHRATCH;
1235
/* Private physics functions */
1237
/* Compute the spin-rate for a trajectory. Different types of throw
1238
(eg, regular thows, bounces, kicks, etc) have different spin
1241
type = type of object
1242
h = trajectory of throwing hand (throws), or next throwing hand (catches)
1243
old = earlier spin to consider
1244
dt = time span of this trajectory
1245
height = height of ball throw or 0 if based on old spin
1246
turns = full club turns required during this operation
1247
togo = partial club turns required to match hands
1250
spinrate(ObjType type, Trajectory *h, double old, double dt,
1251
int height, int turns, double togo)
1253
#ifdef _2DSpinsDontWorkIn3D
1254
const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1259
if(ObjectDefs[type].handle != 0) { /* Clubs */
1260
return (dir * turns * 2 * M_PI + togo) / dt;
1261
} else if(height == 0) { /* Balls already spinning */
1263
} else { /* Balls */
1264
return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1269
/* compute the angle at the end of a spinning trajectory */
1271
end_spin(Trajectory *t)
1273
return t->angle + t->spin * (t->finish - t->start);
1276
/* Sets the initial angle of the catch following hand movement t to
1277
the final angle of the throw n. Also sets the angle of the
1278
subsequent throw to the same angle plus half a turn. */
1280
match_spins_on_catch(Trajectory *t, Trajectory *n)
1282
if(ObjectDefs[t->balllink->object->type].handle == 0) {
1283
t->balllink->angle = end_spin(n);
1284
if(t->balllink->handlink != NULL) {
1285
#ifdef _2DSpinsDontWorkIn3D
1286
t->balllink->handlink->angle = t->balllink->angle + M_PI;
1288
t->balllink->handlink->angle = t->balllink->angle;
1295
find_bounce(jugglestruct *sp,
1296
double yo, double yf, double yc, double tc, double cor)
1298
double tb, i, dy = 0;
1299
const double e = 1; /* permissible error in yc */
1303
yt = height at catch time after one bounce
1304
one or three roots according to timing
1305
find one by interval bisection
1308
for(i = tc / 2; i > 0.0001; i/=2){
1311
(void) fprintf(stderr, "juggle: bounce div by zero!\n");
1314
dy = (yf - yo)/tb + sp->Gr/2*tb;
1316
yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1319
}else if(yt > yc - e){
1325
if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1332
new_predictor(const Trajectory *t, int start, int finish, double angle)
1335
ADD_ELEMENT(Trajectory, n, t->prev);
1340
n->divisions = t->divisions;
1342
n->status = PREDICTOR;
1350
/* Turn abstract timings into physically appropriate object trajectories. */
1352
projectile(jugglestruct *sp)
1355
const int yf = 0; /* Floor height */
1357
for (t = sp->head->next; t != sp->head; t = t->next) {
1358
if (t->status != PTHRATCH || t->action != THROW) {
1360
} else if (t->balllink == NULL) { /* Zero Throw */
1361
t->status = BPREDICTOR;
1362
} else if (t->balllink->handlink == NULL) { /* Incomplete */
1364
} else if(t->balllink == t->handlink) {
1365
/* '2' height - hold on to ball. Don't need to consider
1366
flourishes, 'hands' will do that automatically anyway */
1369
/* Zero spin to avoid wrist injuries */
1371
match_spins_on_catch(t, t);
1373
t->status = BPREDICTOR;
1376
if (t->posn == '_') { /* Bounce once */
1378
const int tb = t->start +
1379
find_bounce(sp, t->y, (double) yf, t->balllink->y,
1380
(double) (t->balllink->start - t->start),
1381
ObjectDefs[t->object->type].cor);
1383
if(tb < t->start) { /* bounce too hard */
1384
t->posn = '+'; /* Use regular throw */
1386
Trajectory *n; /* First (throw) trajectory. */
1387
double dt; /* Time span of a trajectory */
1388
double dy; /* Distance span of a follow-on trajectory.
1389
First trajectory uses t->dy */
1390
/* dx is constant across both trajectories */
1391
t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1393
{ /* ball follows parabola down */
1394
n = new_predictor(t, t->start, tb, t->angle);
1395
if(n == NULL) return False;
1396
dt = n->finish - n->start;
1397
/* Ball rate 4, no flight or matching club turns */
1398
n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1399
t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1400
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1403
{ /* ball follows parabola up */
1404
Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1406
if(m == NULL) return False;
1407
dt = m->finish - m->start;
1408
/* Use previous ball rate, no flight club turns */
1409
m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1410
t->balllink->angle - m->angle);
1411
match_spins_on_catch(t, m);
1412
dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1413
makeParabola(m, t->balllink->x - t->dx * dt,
1414
t->dx, (double) yf, dy, sp->Gr);
1417
t->status = BPREDICTOR;
1420
} else if (t->posn == 'k') { /* Drop & Kick */
1421
Trajectory *n; /* First (drop) trajectory. */
1422
Trajectory *o; /* Second (rest) trajectory */
1423
Trajectory *m; /* Third (kick) trajectory */
1424
const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1425
const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1428
{ /* Fall to ground */
1429
n = new_predictor(t, t->start, td, t->angle);
1430
if(n == NULL) return False;
1431
dt = n->finish - n->start;
1432
/* Ball spin rate 4, no flight club turns */
1433
n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1434
t->balllink->angle - n->angle);
1435
t->dx = (t->balllink->x - t->x) / dt;
1436
t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1437
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1440
{ /* Rest on ground */
1441
o = new_predictor(t, n->finish, tk, end_spin(n));
1442
if(o == NULL) return False;
1444
makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1449
m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1450
if(m == NULL) return False;
1451
dt = m->finish - m->start;
1452
/* Match receiving hand, ball rate 4, one flight club turn */
1453
m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1454
4, 1, t->balllink->angle - m->angle);
1455
match_spins_on_catch(t, m);
1456
dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1457
makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1460
t->status = BPREDICTOR;
1464
/* Regular flight, no bounce */
1465
{ /* ball follows parabola */
1467
Trajectory *n = new_predictor(t, t->start,
1468
t->balllink->start, t->angle);
1469
if(n == NULL) return False;
1470
dt = t->balllink->start - t->start;
1472
n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1473
t->balllink->angle - n->angle);
1474
match_spins_on_catch(t, n);
1475
t->dx = (t->balllink->x - t->x) / dt;
1476
t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1477
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1480
t->status = BPREDICTOR;
1486
/* Turn abstract hand motions into cubic splines. */
1488
hands(jugglestruct *sp)
1490
Trajectory *t, *u, *v;
1492
for (t = sp->head->next; t != sp->head; t = t->next) {
1493
/* no throw => no velocity */
1494
if (t->status != BPREDICTOR) {
1499
if (u == NULL) { /* no next catch */
1503
if (v == NULL) { /* no next throw */
1507
/* double spline takes hand from throw, thru catch, to
1510
t->finish = u->start;
1511
t->status = PREDICTOR;
1513
u->finish = v->start;
1514
u->status = PREDICTOR;
1517
/* FIXME: These adjustments leave a small glitch when alternating
1518
balls and clubs. Just hope no-one notices. :-) */
1520
/* make sure empty hand spin matches the thrown object in case it
1523
t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1524
fabs((u->angle - t->angle)/(u->start - t->start));
1526
u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1527
fabs((v->angle - u->angle)/(v->start - u->start));
1529
(void) makeSplinePair(&t->xp, &u->xp,
1530
t->x, t->dx, t->start,
1532
v->x, v->dx, v->start);
1533
(void) makeSplinePair(&t->yp, &u->yp,
1534
t->y, t->dy, t->start,
1536
v->y, v->dy, v->start);
1538
t->status = PREDICTOR;
1542
/* Given target x, y find_elbow puts hand at target if possible,
1543
* otherwise makes hand point to the target */
1545
find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1549
double x = p->x - s->x;
1550
double y = p->y - s->y;
1551
h2 = x*x + y*y + z*z;
1552
if (h2 > 4 * armlength * armlength) {
1553
t = armlength/sqrt(h2);
1556
h->x = 2 * t * x + s->x;
1557
h->y = 2 * t * y + s->y;
1559
r = sqrt((double)(x*x + z*z));
1560
t = sqrt(4 * armlength * armlength / h2 - 1);
1561
e->x = x*(1 + y*t/r)/2 + s->x;
1562
e->y = (y - r*t)/2 + s->y;
1569
/* NOTE: returned x, y adjusted for arm reach */
1571
reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1573
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1575
find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1576
*p = sp->arm[1][side][HAND] = h;
1577
sp->arm[1][side][ELBOW] = e;
1581
/* dumps a human-readable rendition of the current state of the juggle
1582
pipeline to stderr for debugging */
1584
dump(jugglestruct *sp)
1587
for (t = sp->head->next; t != sp->head; t = t->next) {
1588
switch (t->status) {
1590
(void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1593
(void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1594
t->pattern == NULL?"":t->pattern);
1597
if (t->action == CATCH)
1598
(void) fprintf(stderr, "%p A %c%cC\n",
1600
t->hand ? 'R' : 'L');
1602
(void) fprintf(stderr, "%p A %c%c%c%d\n",
1604
t->hand ? 'R' : 'L',
1605
(t->action == THROW)?'T':'N',
1609
(void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1612
(t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1613
t->height, t->object == NULL?0:t->object->color,
1614
(void*)t->handlink, (void*)t->balllink);
1617
(void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1620
(t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1621
t->height, t->type, t->object == NULL?0:t->object->color,
1622
t->start, t->finish);
1625
(void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1626
(void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1627
t->object == NULL?0:t->object->color,
1628
t->start, t->finish, t->yp.c);
1631
(void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1632
(void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1633
t->object == NULL?0:t->object->color,
1634
t->start, t->finish, t->yp.c);
1637
(void) fprintf(stderr, "%p: status %d not implemented\n",
1638
(void*)t, t->status);
1642
(void) fprintf(stderr, "---\n");
1646
static int get_num_balls(const char *j)
1652
for (p = j; *p; p++) {
1653
if (*p >= '0' && *p <='9') { /* digit */
1654
h = 10*h + (*p - '0');
1666
compare_num_balls(const void *p1, const void *p2)
1669
i = get_num_balls(((patternstruct*)p1)->pattern);
1670
j = get_num_balls(((patternstruct*)p2)->pattern);
1681
/**************************************************************************
1682
* Rendering Functions *
1684
**************************************************************************/
1687
show_arms(ModeInfo * mi)
1690
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1693
XPoint a[countof(sp->arm[0][0])];
1700
for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1701
/* Translate into device coords */
1702
for(i = 0; i < countof(a); i++) {
1703
a[i].x = (short)(SCENE_WIDTH/2 + sp->arm[j][side][i].x*sp->scale);
1704
a[i].y = (short)(SCENE_HEIGHT - sp->arm[j][side][i].y*sp->scale);
1706
sp->arm[0][side][i] = sp->arm[1][side][i];
1709
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1712
polys += tube (a[2].x - (side == LEFT ? soffx : -soffx), a[2].y + soffy, 0,
1713
a[1].x, a[1].y, ARMLENGTH/2,
1714
thickness, 0, slices,
1715
True, True, MI_IS_WIREFRAME(mi));
1718
polys += tube (a[1].x, a[1].y, ARMLENGTH/2,
1719
a[0].x, a[0].y, ARMLENGTH,
1720
thickness * 0.8, 0, slices,
1721
True, True, MI_IS_WIREFRAME(mi));
1723
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1727
glTranslatef (a[2].x - (side == LEFT ? soffx : -soffx),
1731
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1736
glTranslatef (a[1].x, a[1].y, ARMLENGTH/2);
1738
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1743
glTranslatef (a[0].x, a[0].y, ARMLENGTH);
1745
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1753
show_figure(ModeInfo * mi, Bool init)
1756
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1777
/* #### most of this is unused now */
1778
static const XPoint figure[] = {
1779
{ 15, 70}, /* 0 Left Hip */
1780
{ 0, 90}, /* 1 Waist */
1781
{ SX, 130}, /* 2 Left Shoulder */
1782
{-SX, 130}, /* 3 Right Shoulder */
1783
{-15, 70}, /* 4 Right Hip */
1784
{ 0, 130}, /* 5 Neck */
1785
{ 0, 140}, /* 6 Chin */
1786
{ SX, 0}, /* 7 Left Foot */
1787
{-SX, 0}, /* 8 Right Foot */
1788
{-17, 174}, /* 9 Head1 */
1789
{ 17, 140}, /* 10 Head2 */
1791
XPoint a[countof(figure)];
1792
GLfloat gcolor[4] = { 1, 1, 1, 1 };
1794
/* Translate into device coords */
1795
for(i = 0; i < countof(figure); i++) {
1796
a[i].x = (short)(SCENE_WIDTH/2 + (sp->cx + figure[i].x)*sp->scale);
1797
a[i].y = (short)(SCENE_HEIGHT - figure[i].y*sp->scale);
1800
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor);
1803
GLfloat scale = ((GLfloat) a[10].x - a[9].x) / 2;
1808
glTranslatef(a[6].x, a[6].y - scale, 0);
1809
glScalef(scale, scale, scale);
1812
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1815
glScalef(scale, scale, scale);
1816
glTranslatef(0, 0.3, 0);
1818
glTranslatef(0, 0, 0.35);
1819
polys += tube (0, 0, 0,
1822
slices, True, True, MI_IS_WIREFRAME(mi));
1824
glScalef(0.9, 0.9, 1);
1825
polys += unit_sphere(2*slices, 2*slices, MI_IS_WIREFRAME(mi));
1829
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1830
glTranslatef(0, 1.1, 0);
1833
glScalef(scale, scale, scale);
1834
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1838
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1839
glTranslatef(0, 1.1, 0);
1841
glScalef(0.9, 1.0, 0.9);
1842
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1846
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1847
glTranslatef(0, 1.0, 0);
1850
glScalef(scale, scale, scale);
1851
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1855
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1856
glTranslatef(0, 0.8, 0);
1859
glScalef(scale, scale, scale);
1860
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1865
glTranslatef(0, 0.7, 0);
1867
for (i = -1; i <= 1; i += 2) {
1870
glRotatef (i*10, 0, 0, 1);
1871
glTranslatef(-i*0.65, 0, 0);
1874
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1876
glScalef(scale, scale, scale);
1877
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1880
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1882
glTranslatef(0, 0.6, 0);
1883
polys += tube (0, 0, 0,
1886
slices, True, True, MI_IS_WIREFRAME(mi));
1890
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1892
glTranslatef(0, 4.4, 0);
1894
glScalef(scale, scale, scale);
1895
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1899
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1901
glTranslatef(0, 4.7, 0);
1902
polys += tube (0, 0, 0,
1905
slices, True, True, MI_IS_WIREFRAME(mi));
1909
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1911
glTranslatef(0, 9.7, 0);
1913
glScalef(scale, scale, scale);
1914
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1918
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1920
glRotatef (-i*10, 0, 0, 1);
1921
glTranslatef(-i*1.75, 9.7, 0.9);
1923
glScalef (0.4, 1, 1);
1924
polys += tube (0, 0, 0,
1927
slices*4, True, True, MI_IS_WIREFRAME(mi));
1936
sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1937
sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1939
/* Initialise arms */
1941
for(i = 0; i < 2; i++){
1942
sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1943
sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1944
sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1945
sp->arm[i][LEFT][HAND].x = figure[0].x;
1946
sp->arm[i][LEFT][HAND].y = figure[1].y;
1947
sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1948
sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1949
sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1950
sp->arm[i][RIGHT][HAND].x = figure[4].x;
1951
sp->arm[i][RIGHT][HAND].y = figure[1].y;
1957
typedef struct { GLfloat x, y, z; } XYZ;
1959
/* lifted from sphere.c */
1961
striped_unit_sphere (int stacks, int slices,
1963
GLfloat *color1, GLfloat *color2,
1968
double theta1, theta2, theta3;
1970
XYZ la = { 0, 0, 0 }, lb = { 0, 0, 0 };
1971
XYZ c = {0, 0, 0}; /* center */
1972
double r = 1.0; /* radius */
1973
int stacks2 = stacks * 2;
1980
if (slices < 4 || stacks < 2 || r <= 0)
1982
glBegin (GL_POINTS);
1983
glVertex3f (c.x, c.y, c.z);
1990
for (j = 0; j < stacks; j++)
1992
theta1 = j * (M_PI+M_PI) / stacks2 - M_PI_2;
1993
theta2 = (j + 1) * (M_PI+M_PI) / stacks2 - M_PI_2;
1995
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
1996
((j == 0 || j == stacks-1 ||
1997
j % (stacks / (stripes+1)))
1998
? color1 : color2));
2000
glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
2001
for (i = 0; i <= slices; i++)
2003
theta3 = i * (M_PI+M_PI) / slices;
2005
if (wire_p && i != 0)
2007
glVertex3f (lb.x, lb.y, lb.z);
2008
glVertex3f (la.x, la.y, la.z);
2011
e.x = cos (theta2) * cos(theta3);
2013
e.z = cos (theta2) * sin(theta3);
2014
p.x = c.x + r * e.x;
2015
p.y = c.y + r * e.y;
2016
p.z = c.z + r * e.z;
2018
glNormal3f (e.x, e.y, e.z);
2019
glTexCoord2f (i / (double)slices,
2020
2*(j+1) / (double)stacks2);
2021
glVertex3f (p.x, p.y, p.z);
2024
e.x = cos(theta1) * cos(theta3);
2026
e.z = cos(theta1) * sin(theta3);
2027
p.x = c.x + r * e.x;
2028
p.y = c.y + r * e.y;
2029
p.z = c.z + r * e.z;
2031
glNormal3f (e.x, e.y, e.z);
2032
glTexCoord2f (i / (double)slices,
2033
2*j / (double)stacks2);
2034
glVertex3f (p.x, p.y, p.z);
2046
show_ball(ModeInfo *mi, unsigned long color, Trace *s)
2049
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2050
/*int offset = (int)(s->angle*64*180/M_PI);*/
2051
short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2052
short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2053
GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2054
GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2057
/* Avoid wrapping */
2058
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2060
gcolor1[0] = mi->colors[color].red / 65536.0;
2061
gcolor1[1] = mi->colors[color].green / 65536.0;
2062
gcolor1[2] = mi->colors[color].blue / 65536.0;
2064
gcolor2[0] = gcolor1[0] / 3;
2065
gcolor2[1] = gcolor1[1] / 3;
2066
gcolor2[2] = gcolor1[2] / 3;
2069
GLfloat scale = BALLRADIUS;
2071
glTranslatef(x, y, 0);
2072
glScalef(scale, scale, scale);
2074
glRotatef (s->angle / M_PI*180, 1, 1, 0);
2076
polys += striped_unit_sphere (slices, slices, s->divisions,
2077
gcolor1, gcolor2, MI_IS_WIREFRAME(mi));
2084
show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
2087
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2088
/*int offset = (int)(s->angle*64*180/M_PI);*/
2089
short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2090
short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2091
double radius = 12 * sp->scale;
2092
GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2093
GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2095
int divs = s->divisions;
2116
/* Avoid wrapping */
2117
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2119
gcolor1[0] = mi->colors[color].red / 65536.0;
2120
gcolor1[1] = mi->colors[color].green / 65536.0;
2121
gcolor1[2] = mi->colors[color].blue / 65536.0;
2124
GLfloat scale = radius;
2126
glTranslatef(x, y, 0);
2127
glScalef(scale, scale, scale);
2129
glTranslatef (0, 0, 2); /* put end of handle in hand */
2131
glRotatef (s->angle / M_PI*180, 1, 0, 0);
2134
glScalef (0.5, 1, 0.5);
2135
polys += striped_unit_sphere (slices, slices, divs, gcolor2, gcolor1,
2136
MI_IS_WIREFRAME(mi));
2138
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2139
polys += tube (0, 0, 0,
2142
slices, True, True, MI_IS_WIREFRAME(mi));
2144
glTranslatef (0, 2, 0);
2145
glScalef (0.25, 0.25, 0.25);
2146
polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
2155
show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2159
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2160
XPoint head, tail, last;
2161
DXPoint dhead, dlast;
2162
const double sa = sin(s->angle);
2163
const double ca = cos(s->angle);
2165
const double TailLen = -24;
2166
const double HeadLen = 16;
2167
const short Width = (short)(5 * sqrt(sp->scale));
2179
dhead.x = s->x + HeadLen * PERSPEC * sa;
2180
dhead.y = s->y - HeadLen * ca;
2182
if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2184
} else { /* Store 'last' so we can use it later when s->prev has
2186
if(s->prev != s->next) {
2187
dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2188
dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2195
/* Avoid wrapping (after last is stored) */
2196
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2198
head.x = (short)(SCENE_WIDTH/2 + dhead.x*sp->scale);
2199
head.y = (short)(SCENE_HEIGHT - dhead.y*sp->scale);
2201
last.x = (short)(SCENE_WIDTH/2 + dlast.x*sp->scale);
2202
last.y = (short)(SCENE_HEIGHT - dlast.y*sp->scale);
2204
tail.x = (short)(SCENE_WIDTH/2 +
2205
(s->x + TailLen * PERSPEC * sa)*sp->scale );
2206
tail.y = (short)(SCENE_HEIGHT - (s->y - TailLen * ca)*sp->scale );
2208
if(color != MI_BLACK_PIXEL(mi)) {
2209
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2210
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2211
Width, LineSolid, CapRound, JoinRound);
2212
draw_line(mi, head.x, head.y, tail.x, tail.y);
2214
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2215
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2216
Width * 2, LineSolid, CapRound, JoinRound);
2218
draw_line(mi, head.x, head.y, last.x, last.y);
2226
show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2229
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2230
/*int offset = (int)(s->angle*64*180/M_PI);*/
2231
short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2232
short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2233
GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2234
GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2237
/* Avoid wrapping */
2238
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2240
gcolor1[0] = mi->colors[color].red / 65536.0;
2241
gcolor1[1] = mi->colors[color].green / 65536.0;
2242
gcolor1[2] = mi->colors[color].blue / 65536.0;
2245
glTranslatef(x, y, 0);
2248
glTranslatef (0, 0, 2); /* put end of handle in hand */
2249
glRotatef (s->angle / M_PI*180, 1, 0, 0);
2251
glScalef (0.3, 1, 1); /* flatten blade */
2253
glTranslatef(0, 6, 0);
2254
glRotatef (180, 1, 0, 0);
2256
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2257
polys += tube (0, 0, 0,
2260
slices, True, True, MI_IS_WIREFRAME(mi));
2262
glTranslatef (0, 12, 0);
2263
glScalef (0.7, 10, 0.7);
2264
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2265
polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2273
show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2276
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2277
/*int offset = (int)(s->angle*64*180/M_PI);*/
2278
short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2279
short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2280
double radius = 12 * sp->scale;
2281
GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2282
GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2285
int wire_p = MI_IS_WIREFRAME(mi);
2286
GLfloat width = M_PI * 2 / slices;
2289
GLfloat thickness = 0.15;
2291
/* Avoid wrapping */
2292
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2294
gcolor1[0] = mi->colors[color].red / 65536.0;
2295
gcolor1[1] = mi->colors[color].green / 65536.0;
2296
gcolor1[2] = mi->colors[color].blue / 65536.0;
2298
gcolor2[0] = gcolor1[0] / 3;
2299
gcolor2[1] = gcolor1[1] / 3;
2300
gcolor2[2] = gcolor1[2] / 3;
2303
glTranslatef(0, 0, 12); /* back of ring in hand */
2305
glTranslatef(x, y, 0);
2306
glScalef(radius, radius, radius);
2308
glRotatef (90, 0, 1, 0);
2309
glRotatef (s->angle / M_PI*180, 0, 0, 1);
2311
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2314
for (j = -1; j <= 1; j += 2)
2316
GLfloat z = j * thickness/2;
2317
glFrontFace (j < 0 ? GL_CCW : GL_CW);
2318
glNormal3f (0, 0, j*1);
2319
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2320
for (i = 0; i < slices + (wire_p ? 0 : 1); i++) {
2321
GLfloat th, cth, sth;
2322
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
2323
(i % (slices/3) ? gcolor1 : gcolor2));
2327
glVertex3f (cth * ra, sth * ra, z);
2328
glVertex3f (cth * rb, sth * rb, z);
2335
glFrontFace (GL_CCW);
2336
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2337
for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2339
GLfloat th = i * width;
2340
GLfloat cth = cos(th);
2341
GLfloat sth = sin(th);
2342
glNormal3f (cth, sth, 0);
2343
glVertex3f (cth * ra, sth * ra, thickness/2);
2344
glVertex3f (cth * ra, sth * ra, -thickness/2);
2350
glFrontFace (GL_CW);
2351
glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2352
for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2354
GLfloat th = i * width;
2355
GLfloat cth = cos(th);
2356
GLfloat sth = sin(th);
2357
glNormal3f (-cth, -sth, 0);
2358
glVertex3f (cth * rb, sth * ra, thickness/2);
2359
glVertex3f (cth * rb, sth * ra, -thickness/2);
2364
glFrontFace (GL_CCW);
2371
show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2374
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2375
/*int offset = (int)(s->angle*64*180/M_PI);*/
2376
short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2377
short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2378
double radius = 12 * sp->scale;
2379
GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2380
GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2384
/* Avoid wrapping */
2385
if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2387
gcolor1[0] = mi->colors[color].red / 65536.0;
2388
gcolor1[1] = mi->colors[color].green / 65536.0;
2389
gcolor1[2] = mi->colors[color].blue / 65536.0;
2392
GLfloat scale = radius;
2395
glTranslatef(0, -6, 5); /* position on top of hand */
2397
glTranslatef(x, y, 0);
2398
glScalef(scale, scale, scale);
2399
glRotatef (s->angle / M_PI*180, 1, 0, 1);
2401
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2402
polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2404
glRotatef (90, 0, 0, 1);
2405
glTranslatef (0, 0, 0.81);
2406
glScalef(0.15, 0.15, 0.15);
2407
glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2408
for (i = 0; i < 3; i++) {
2410
glTranslatef (0, 0, 1);
2411
glRotatef (360 * i / 3, 0, 0, 1);
2412
glTranslatef (2, 0, 0);
2413
glRotatef (18, 0, 1, 0);
2414
glBegin (MI_IS_WIREFRAME(mi) ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
2415
glVertex3f (0, 0, 0);
2416
for (j = slices; j >= 0; j--) {
2417
GLfloat th = j * M_PI*2 / slices;
2418
glVertex3f (cos(th), sin(th), 0);
2431
/**************************************************************************
2432
* Public Functions *
2434
**************************************************************************/
2438
release_juggle (ModeInfo * mi)
2440
if (juggles != NULL) {
2443
for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2444
free_juggle(&juggles[screen]);
2446
juggles = (jugglestruct *) NULL;
2450
/* FIXME: refill_juggle currently just appends new throws to the
2451
* programme. This is fine if the programme is empty, but if there
2452
* are still some trajectories left then it really should take these
2456
refill_juggle(ModeInfo * mi)
2458
jugglestruct *sp = NULL;
2461
if (juggles == NULL)
2463
sp = &juggles[MI_SCREEN(mi)];
2465
/* generate pattern */
2467
if (pattern == NULL) {
2470
#define MAXREPEAT 300
2471
#define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2472
#define POSITION_BIAS 20 /* larger makes hand movements less likely */
2475
while (count < MI_CYCLES(mi)) {
2476
char buf[MAXPAT * 3 + 3], *b = buf;
2478
int l = NRAND(MAXPAT) + 1;
2479
int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2481
{ /* vary number of balls */
2482
int new_balls = sp->num_balls;
2485
if (new_balls == 2) /* Do not juggle 2 that often */
2486
change = NRAND(2 + CHANGE_BIAS / 4);
2488
change = NRAND(2 + CHANGE_BIAS);
2499
if (new_balls < sp->patternindex.minballs) {
2502
if (new_balls > sp->patternindex.maxballs) {
2505
if (new_balls < sp->num_balls) {
2506
if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2509
sp->num_balls = new_balls;
2513
if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2514
/* Pick from PortFolio */
2515
int p = sp->patternindex.index[sp->num_balls].start +
2516
NRAND(sp->patternindex.index[sp->num_balls].number);
2517
if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2520
/* Invent a new pattern */
2522
for(i = 0; i < l; i++){
2524
do { /* Triangular Distribution => high values more likely */
2525
m = NRAND(sp->num_balls + 1);
2526
n = NRAND(sp->num_balls + 1);
2528
if (n == sp->num_balls) {
2531
switch(NRAND(5 + POSITION_BIAS)){
2532
case 0: /* Outside throw */
2534
case 1: /* Cross throw */
2536
case 2: /* Cross catch */
2538
case 3: /* Cross throw and catch */
2540
case 4: /* Bounce */
2543
break; /* Inside throw (default) */
2552
if (!program(mi, buf, NULL, t))
2557
} else { /* pattern supplied in height or 'a' notation */
2558
if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2575
if (!projectile(sp)) {
2582
if(MI_IS_DEBUG(mi)) dump(sp);
2587
change_juggle(ModeInfo * mi)
2589
jugglestruct *sp = NULL;
2592
if (juggles == NULL)
2594
sp = &juggles[MI_SCREEN(mi)];
2596
/* Strip pending trajectories */
2597
for (t = sp->head->next; t != sp->head; t = t->next) {
2598
if(t->start > sp->time || t->finish < sp->time) {
2601
trajectory_destroy(n);
2605
/* Pick the current object theme */
2606
sp->objtypes = choose_object();
2610
mi->polygon_count += show_figure(mi, True);
2615
reshape_juggle (ModeInfo *mi, int width, int height)
2617
GLfloat h = (GLfloat) height / (GLfloat) width;
2619
glViewport (0, 0, (GLint) width, (GLint) height);
2621
glMatrixMode(GL_PROJECTION);
2623
gluPerspective (30.0, 1/h, 1.0, 100.0);
2625
glMatrixMode(GL_MODELVIEW);
2627
gluLookAt( 0.0, 0.0, 30.0,
2631
glClear(GL_COLOR_BUFFER_BIT);
2636
init_juggle (ModeInfo * mi)
2638
jugglestruct *sp = 0;
2639
int wire = MI_IS_WIREFRAME(mi);
2642
juggles = (jugglestruct *)
2643
calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct));
2645
fprintf(stderr, "%s: out of memory\n", progname);
2650
sp = &juggles[MI_SCREEN(mi)];
2652
sp->glx_context = init_GL(mi);
2654
load_font (mi->dpy, "titleFont", &sp->mode_font, &sp->font_dlist);
2656
reshape_juggle (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2660
GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
2661
GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
2662
GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
2663
GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
2665
glEnable(GL_LIGHTING);
2666
glEnable(GL_LIGHT0);
2667
glEnable(GL_DEPTH_TEST);
2668
glEnable(GL_CULL_FACE);
2670
glLightfv(GL_LIGHT0, GL_POSITION, pos);
2671
glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2672
glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2673
glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2676
make_random_colormap (0, 0, 0,
2677
mi->colors, &MI_NPIXELS(mi),
2678
True, False, 0, False);
2681
double spin_speed = 0.05;
2682
double wander_speed = 0.001;
2683
double spin_accel = 0.05;
2684
sp->rot = make_rotator (0, spin_speed, 0,
2685
spin_accel, wander_speed, False);
2686
sp->trackball = gltrackball_init ();
2689
if (only && *only && strcmp(only, " ")) {
2690
balls = clubs = torches = knives = rings = bballs = False;
2691
if (!strcasecmp (only, "balls")) balls = True;
2692
else if (!strcasecmp (only, "clubs")) clubs = True;
2693
else if (!strcasecmp (only, "torches")) torches = True;
2694
else if (!strcasecmp (only, "knives")) knives = True;
2695
else if (!strcasecmp (only, "rings")) rings = True;
2696
else if (!strcasecmp (only, "bballs")) bballs = True;
2698
(void) fprintf (stderr,
2699
"Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2700
"\t rings, or bballs (not \"%s\")\n", only);
2701
#ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2707
/* #### hard to make this look good in OpenGL... */
2711
if (sp->head == 0) { /* first time initializing this juggler */
2713
sp->count = ABS(MI_COUNT(mi));
2717
/* record start time */
2718
sp->begintime = time(NULL);
2719
if(sp->patternindex.maxballs > 0) {
2720
sp->num_balls = sp->patternindex.minballs +
2721
NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2724
mi->polygon_count +=
2725
show_figure(mi, True); /* Draw figure. Also discovers
2726
information about the juggler's
2729
/* "7" should be about three times the height of the juggler's
2731
sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2732
7 * THROW_CATCH_INTERVAL);
2734
if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2735
balls = True; /* Have to juggle something! */
2737
/* create circular trajectory list */
2738
ADD_ELEMENT(Trajectory, sp->head, sp->head);
2739
if(sp->head == NULL){
2744
/* create circular object list */
2745
ADD_ELEMENT(Object, sp->objects, sp->objects);
2746
if(sp->objects == NULL){
2751
sp->pattern = strdup(""); /* Initialise saved pattern with
2755
sp = &juggles[MI_SCREEN(mi)];
2759
!strcasecmp (pattern, ".") ||
2760
!strcasecmp (pattern, "random")))
2763
if (pattern == NULL && sp->patternindex.maxballs == 0) {
2764
/* pattern list needs indexing */
2765
int nelements = countof(portfolio);
2769
/* sort according to number of balls */
2770
qsort((void*)portfolio, nelements,
2771
sizeof(portfolio[1]), compare_num_balls);
2773
/* last pattern has most balls */
2774
sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2775
/* run through sorted list, indexing start of each group
2776
and number in group */
2777
sp->patternindex.maxballs = 1;
2778
for (i = 0; i < nelements; i++) {
2779
int b = get_num_balls(portfolio[i].pattern);
2780
if (b > sp->patternindex.maxballs) {
2781
sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2782
if(numpat == 0) sp->patternindex.minballs = b;
2783
sp->patternindex.maxballs = b;
2785
sp->patternindex.index[sp->patternindex.maxballs].start = i;
2790
sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2793
/* Set up programme */
2796
/* Only put things here that won't interrupt the programme during
2799
/* Use MIN so that users can resize in interesting ways, eg
2800
narrow windows for tall patterns, etc */
2801
sp->scale = MIN(SCENE_HEIGHT/480.0, SCENE_WIDTH/160.0);
2806
juggle_handle_event (ModeInfo *mi, XEvent *event)
2808
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2810
if (event->xany.type == ButtonPress &&
2811
event->xbutton.button == Button1)
2813
sp->button_down_p = True;
2814
gltrackball_start (sp->trackball,
2815
event->xbutton.x, event->xbutton.y,
2816
MI_WIDTH (mi), MI_HEIGHT (mi));
2819
else if (event->xany.type == ButtonRelease &&
2820
event->xbutton.button == Button1)
2822
sp->button_down_p = False;
2825
else if (event->xany.type == ButtonPress &&
2826
(event->xbutton.button == Button4 ||
2827
event->xbutton.button == Button5 ||
2828
event->xbutton.button == Button6 ||
2829
event->xbutton.button == Button7))
2831
gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
2832
!!event->xbutton.state);
2835
else if (event->xany.type == MotionNotify &&
2838
gltrackball_track (sp->trackball,
2839
event->xmotion.x, event->xmotion.y,
2840
MI_WIDTH (mi), MI_HEIGHT (mi));
2843
else if (event->xany.type == KeyPress)
2847
XLookupString (&event->xkey, &c, 1, &keysym, 0);
2848
if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
2861
draw_juggle (ModeInfo *mi)
2863
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2864
Display *dpy = MI_DISPLAY(mi);
2865
Window window = MI_WINDOW(mi);
2867
Trajectory *traj = NULL;
2869
unsigned long future = 0;
2870
char *pattern = NULL;
2872
if (!sp->glx_context)
2875
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
2877
glShadeModel(GL_SMOOTH);
2879
glEnable(GL_DEPTH_TEST);
2880
glEnable(GL_NORMALIZE);
2881
glEnable(GL_CULL_FACE);
2883
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2887
glTranslatef(0,-3,0);
2891
get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
2892
glTranslatef((x - 0.5) * 8,
2896
gltrackball_rotate (sp->trackball);
2898
get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
2900
if (y < 0.8) y = 0.8 - (y - 0.8); /* always face forward */
2901
if (y > 1.2) y = 1.2 - (y - 1.2);
2903
glRotatef (x * 360, 1.0, 0.0, 0.0);
2904
glRotatef (y * 360, 0.0, 1.0, 0.0);
2905
glRotatef (z * 360, 0.0, 0.0, 1.0);
2909
GLfloat scale = 20.0 / SCENE_HEIGHT;
2910
glScalef(scale, scale, scale);
2913
glRotatef (180, 0, 0, 1);
2914
glTranslatef(-SCENE_WIDTH/2, -SCENE_HEIGHT/2, 0);
2915
glTranslatef(0, -150, 0);
2917
mi->polygon_count = 0;
2922
(void)gettimeofday(&tv, NULL);
2923
sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2925
sp->time += MI_DELAY(mi) / 1000;
2928
/* First pass: Move arms and strip out expired elements */
2929
for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2930
if (traj->status != PREDICTOR) {
2931
/* Skip any elements that need further processing */
2932
/* We could remove them, but there shoudn't be many and they
2933
would be needed if we ever got the pattern refiller
2937
if (traj->start > future) { /* Lookahead to the end of the show */
2938
future = traj->start;
2940
if (sp->time < traj->start) { /* early */
2942
} else if (sp->time < traj->finish) { /* working */
2944
/* Look for pattern name */
2945
if(traj->pattern != NULL) {
2946
pattern=traj->pattern;
2949
if (traj->type == Empty || traj->type == Full) {
2950
/* Only interested in hands on this pass */
2951
/* double angle = traj->angle + traj->spin * (sp->time - traj->start);*/
2952
double xd = 0, yd = 0;
2955
/* Find the catching offset */
2956
if(traj->object != NULL) {
2958
/* #### not sure what this is doing, but I'm guessing
2959
that the use of PERSPEC means this isn't needed
2960
in the OpenGL version? -jwz
2962
if(ObjectDefs[traj->object->type].handle > 0) {
2963
/* Handles Need to be oriented */
2964
xd = ObjectDefs[traj->object->type].handle *
2965
PERSPEC * sin(angle);
2966
yd = ObjectDefs[traj->object->type].handle *
2971
/* Balls are always caught at the bottom */
2976
p.x = (CUBIC(traj->xp, sp->time) - xd);
2977
p.y = (CUBIC(traj->yp, sp->time) + yd);
2978
reach_arm(mi, traj->hand, &p);
2980
/* Store updated hand position */
2984
if (traj->type == Ball || traj->type == Full) {
2985
/* Only interested in objects on this pass */
2989
if(traj->type == Full) {
2990
/* Adjusted these in the first pass */
2994
x = CUBIC(traj->xp, sp->time);
2995
y = CUBIC(traj->yp, sp->time);
2998
ADD_ELEMENT(Trace, s, traj->object->trace->prev);
3001
s->angle = traj->angle + traj->spin * (sp->time - traj->start);
3002
s->divisions = traj->divisions;
3003
traj->object->tracelen++;
3004
traj->object->active = True;
3006
} else { /* expired */
3007
Trajectory *n = traj;
3009
trajectory_destroy(n);
3014
mi->polygon_count += show_figure(mi, False);
3015
mi->polygon_count += show_arms(mi);
3018
glTranslatef(0, 0, ARMLENGTH);
3019
for (o = sp->objects->next; o != sp->objects; o = o->next) {
3021
mi->polygon_count += ObjectDefs[o->type].draw(mi, o->color,
3028
/* Save pattern name so we can erase it when it changes */
3029
if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
3031
sp->pattern = strdup(pattern);
3033
if (MI_IS_VERBOSE(mi)) {
3034
(void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
3035
MI_SCREEN(mi), sp->pattern);
3039
if(sp->mode_font != None) {
3040
print_gl_string (mi->dpy, sp->mode_font, sp->font_dlist,
3041
mi->xgwa.width, mi->xgwa.height,
3042
10, mi->xgwa.height - 10,
3043
sp->pattern, False);
3047
if((int)(sp->time/10) % 1000 == 0)
3048
(void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
3051
if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
3053
} else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
3060
if (mi->fps_p) do_fps (mi);
3063
glXSwapBuffers(dpy, window);
3066
XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle)