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
140 #define countof(x) (sizeof((x))/sizeof((*x)))
142 #include "xlockmore.h"
146 #include "gltrackball.h"
147 #include "glxfonts.h"
150 #ifdef USE_GL /* whole file */
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])];
1699 glFrontFace(GL_CCW);
1702 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1703 /* Translate into device coords */
1704 for(i = 0; i < countof(a); i++) {
1705 a[i].x = (short)(SCENE_WIDTH/2 + sp->arm[j][side][i].x*sp->scale);
1706 a[i].y = (short)(SCENE_HEIGHT - sp->arm[j][side][i].y*sp->scale);
1708 sp->arm[0][side][i] = sp->arm[1][side][i];
1711 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1714 polys += tube (a[2].x - (side == LEFT ? soffx : -soffx), a[2].y + soffy, 0,
1715 a[1].x, a[1].y, ARMLENGTH/2,
1716 thickness, 0, slices,
1717 True, True, MI_IS_WIREFRAME(mi));
1720 polys += tube (a[1].x, a[1].y, ARMLENGTH/2,
1721 a[0].x, a[0].y, ARMLENGTH,
1722 thickness * 0.8, 0, slices,
1723 True, True, MI_IS_WIREFRAME(mi));
1725 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1729 glTranslatef (a[2].x - (side == LEFT ? soffx : -soffx),
1733 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1738 glTranslatef (a[1].x, a[1].y, ARMLENGTH/2);
1740 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1745 glTranslatef (a[0].x, a[0].y, ARMLENGTH);
1747 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1755 show_figure(ModeInfo * mi, Bool init)
1758 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1779 /* #### most of this is unused now */
1780 static const XPoint figure[] = {
1781 { 15, 70}, /* 0 Left Hip */
1782 { 0, 90}, /* 1 Waist */
1783 { SX, 130}, /* 2 Left Shoulder */
1784 {-SX, 130}, /* 3 Right Shoulder */
1785 {-15, 70}, /* 4 Right Hip */
1786 { 0, 130}, /* 5 Neck */
1787 { 0, 140}, /* 6 Chin */
1788 { SX, 0}, /* 7 Left Foot */
1789 {-SX, 0}, /* 8 Right Foot */
1790 {-17, 174}, /* 9 Head1 */
1791 { 17, 140}, /* 10 Head2 */
1793 XPoint a[countof(figure)];
1794 GLfloat gcolor[4] = { 1, 1, 1, 1 };
1796 /* Translate into device coords */
1797 for(i = 0; i < countof(figure); i++) {
1798 a[i].x = (short)(SCENE_WIDTH/2 + (sp->cx + figure[i].x)*sp->scale);
1799 a[i].y = (short)(SCENE_HEIGHT - figure[i].y*sp->scale);
1802 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor);
1804 glFrontFace(GL_CCW);
1807 GLfloat scale = ((GLfloat) a[10].x - a[9].x) / 2;
1812 glTranslatef(a[6].x, a[6].y - scale, 0);
1813 glScalef(scale, scale, scale);
1816 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1819 glScalef(scale, scale, scale);
1820 glTranslatef(0, 0.3, 0);
1822 glTranslatef(0, 0, 0.35);
1823 polys += tube (0, 0, 0,
1826 slices, True, True, MI_IS_WIREFRAME(mi));
1828 glScalef(0.9, 0.9, 1);
1829 polys += unit_sphere(2*slices, 2*slices, MI_IS_WIREFRAME(mi));
1833 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1834 glTranslatef(0, 1.1, 0);
1837 glScalef(scale, scale, scale);
1838 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1842 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1843 glTranslatef(0, 1.1, 0);
1845 glScalef(0.9, 1.0, 0.9);
1846 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1850 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1851 glTranslatef(0, 1.0, 0);
1854 glScalef(scale, scale, scale);
1855 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1859 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1860 glTranslatef(0, 0.8, 0);
1863 glScalef(scale, scale, scale);
1864 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1869 glTranslatef(0, 0.7, 0);
1871 for (i = -1; i <= 1; i += 2) {
1874 glRotatef (i*10, 0, 0, 1);
1875 glTranslatef(-i*0.65, 0, 0);
1878 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1880 glScalef(scale, scale, scale);
1881 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1884 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1886 glTranslatef(0, 0.6, 0);
1887 polys += tube (0, 0, 0,
1890 slices, True, True, MI_IS_WIREFRAME(mi));
1894 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1896 glTranslatef(0, 4.4, 0);
1898 glScalef(scale, scale, scale);
1899 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1903 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1905 glTranslatef(0, 4.7, 0);
1906 polys += tube (0, 0, 0,
1909 slices, True, True, MI_IS_WIREFRAME(mi));
1913 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1915 glTranslatef(0, 9.7, 0);
1917 glScalef(scale, scale, scale);
1918 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1922 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1924 glRotatef (-i*10, 0, 0, 1);
1925 glTranslatef(-i*1.75, 9.7, 0.9);
1927 glScalef (0.4, 1, 1);
1928 polys += tube (0, 0, 0,
1931 slices*4, True, True, MI_IS_WIREFRAME(mi));
1940 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1941 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1943 /* Initialise arms */
1945 for(i = 0; i < 2; i++){
1946 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1947 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1948 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1949 sp->arm[i][LEFT][HAND].x = figure[0].x;
1950 sp->arm[i][LEFT][HAND].y = figure[1].y;
1951 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1952 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1953 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1954 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1955 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1961 typedef struct { GLfloat x, y, z; } XYZ;
1963 /* lifted from sphere.c */
1965 striped_unit_sphere (int stacks, int slices,
1967 GLfloat *color1, GLfloat *color2,
1972 double theta1, theta2, theta3;
1974 XYZ la = { 0, 0, 0 }, lb = { 0, 0, 0 };
1975 XYZ c = {0, 0, 0}; /* center */
1976 double r = 1.0; /* radius */
1977 int stacks2 = stacks * 2;
1984 if (slices < 4 || stacks < 2 || r <= 0)
1986 glBegin (GL_POINTS);
1987 glVertex3f (c.x, c.y, c.z);
1994 for (j = 0; j < stacks; j++)
1996 theta1 = j * (M_PI+M_PI) / stacks2 - M_PI_2;
1997 theta2 = (j + 1) * (M_PI+M_PI) / stacks2 - M_PI_2;
1999 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
2000 ((j == 0 || j == stacks-1 ||
2001 j % (stacks / (stripes+1)))
2002 ? color1 : color2));
2004 glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
2005 for (i = 0; i <= slices; i++)
2007 theta3 = i * (M_PI+M_PI) / slices;
2009 if (wire_p && i != 0)
2011 glVertex3f (lb.x, lb.y, lb.z);
2012 glVertex3f (la.x, la.y, la.z);
2015 e.x = cos (theta2) * cos(theta3);
2017 e.z = cos (theta2) * sin(theta3);
2018 p.x = c.x + r * e.x;
2019 p.y = c.y + r * e.y;
2020 p.z = c.z + r * e.z;
2022 glNormal3f (e.x, e.y, e.z);
2023 glTexCoord2f (i / (double)slices,
2024 2*(j+1) / (double)stacks2);
2025 glVertex3f (p.x, p.y, p.z);
2028 e.x = cos(theta1) * cos(theta3);
2030 e.z = cos(theta1) * sin(theta3);
2031 p.x = c.x + r * e.x;
2032 p.y = c.y + r * e.y;
2033 p.z = c.z + r * e.z;
2035 glNormal3f (e.x, e.y, e.z);
2036 glTexCoord2f (i / (double)slices,
2037 2*j / (double)stacks2);
2038 glVertex3f (p.x, p.y, p.z);
2050 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
2053 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2054 /*int offset = (int)(s->angle*64*180/M_PI);*/
2055 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2056 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2057 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2058 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2061 /* Avoid wrapping */
2062 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2064 gcolor1[0] = mi->colors[color].red / 65536.0;
2065 gcolor1[1] = mi->colors[color].green / 65536.0;
2066 gcolor1[2] = mi->colors[color].blue / 65536.0;
2068 gcolor2[0] = gcolor1[0] / 3;
2069 gcolor2[1] = gcolor1[1] / 3;
2070 gcolor2[2] = gcolor1[2] / 3;
2072 glFrontFace(GL_CCW);
2075 GLfloat scale = BALLRADIUS;
2077 glTranslatef(x, y, 0);
2078 glScalef(scale, scale, scale);
2080 glRotatef (s->angle / M_PI*180, 1, 1, 0);
2082 polys += striped_unit_sphere (slices, slices, s->divisions,
2083 gcolor1, gcolor2, MI_IS_WIREFRAME(mi));
2090 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
2093 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2094 /*int offset = (int)(s->angle*64*180/M_PI);*/
2095 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2096 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2097 double radius = 12 * sp->scale;
2098 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2099 GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2101 int divs = s->divisions;
2122 /* Avoid wrapping */
2123 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2125 gcolor1[0] = mi->colors[color].red / 65536.0;
2126 gcolor1[1] = mi->colors[color].green / 65536.0;
2127 gcolor1[2] = mi->colors[color].blue / 65536.0;
2129 glFrontFace(GL_CCW);
2132 GLfloat scale = radius;
2134 glTranslatef(x, y, 0);
2135 glScalef(scale, scale, scale);
2137 glTranslatef (0, 0, 2); /* put end of handle in hand */
2139 glRotatef (s->angle / M_PI*180, 1, 0, 0);
2142 glScalef (0.5, 1, 0.5);
2143 polys += striped_unit_sphere (slices, slices, divs, gcolor2, gcolor1,
2144 MI_IS_WIREFRAME(mi));
2146 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2147 polys += tube (0, 0, 0,
2150 slices, True, True, MI_IS_WIREFRAME(mi));
2152 glTranslatef (0, 2, 0);
2153 glScalef (0.25, 0.25, 0.25);
2154 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
2163 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2167 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2168 XPoint head, tail, last;
2169 DXPoint dhead, dlast;
2170 const double sa = sin(s->angle);
2171 const double ca = cos(s->angle);
2173 const double TailLen = -24;
2174 const double HeadLen = 16;
2175 const short Width = (short)(5 * sqrt(sp->scale));
2187 dhead.x = s->x + HeadLen * PERSPEC * sa;
2188 dhead.y = s->y - HeadLen * ca;
2190 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2192 } else { /* Store 'last' so we can use it later when s->prev has
2194 if(s->prev != s->next) {
2195 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2196 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2203 /* Avoid wrapping (after last is stored) */
2204 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2206 head.x = (short)(SCENE_WIDTH/2 + dhead.x*sp->scale);
2207 head.y = (short)(SCENE_HEIGHT - dhead.y*sp->scale);
2209 last.x = (short)(SCENE_WIDTH/2 + dlast.x*sp->scale);
2210 last.y = (short)(SCENE_HEIGHT - dlast.y*sp->scale);
2212 tail.x = (short)(SCENE_WIDTH/2 +
2213 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2214 tail.y = (short)(SCENE_HEIGHT - (s->y - TailLen * ca)*sp->scale );
2216 if(color != MI_BLACK_PIXEL(mi)) {
2217 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2218 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2219 Width, LineSolid, CapRound, JoinRound);
2220 draw_line(mi, head.x, head.y, tail.x, tail.y);
2222 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2223 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2224 Width * 2, LineSolid, CapRound, JoinRound);
2226 draw_line(mi, head.x, head.y, last.x, last.y);
2234 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2237 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2238 /*int offset = (int)(s->angle*64*180/M_PI);*/
2239 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2240 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2241 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2242 GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2245 /* Avoid wrapping */
2246 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2248 gcolor1[0] = mi->colors[color].red / 65536.0;
2249 gcolor1[1] = mi->colors[color].green / 65536.0;
2250 gcolor1[2] = mi->colors[color].blue / 65536.0;
2252 glFrontFace(GL_CCW);
2255 glTranslatef(x, y, 0);
2258 glTranslatef (0, 0, 2); /* put end of handle in hand */
2259 glRotatef (s->angle / M_PI*180, 1, 0, 0);
2261 glScalef (0.3, 1, 1); /* flatten blade */
2263 glTranslatef(0, 6, 0);
2264 glRotatef (180, 1, 0, 0);
2266 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2267 polys += tube (0, 0, 0,
2270 slices, True, True, MI_IS_WIREFRAME(mi));
2272 glTranslatef (0, 12, 0);
2273 glScalef (0.7, 10, 0.7);
2274 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2275 polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2283 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2286 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2287 /*int offset = (int)(s->angle*64*180/M_PI);*/
2288 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2289 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2290 double radius = 12 * sp->scale;
2291 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2292 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2295 int wire_p = MI_IS_WIREFRAME(mi);
2296 GLfloat width = M_PI * 2 / slices;
2299 GLfloat thickness = 0.15;
2301 /* Avoid wrapping */
2302 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2304 gcolor1[0] = mi->colors[color].red / 65536.0;
2305 gcolor1[1] = mi->colors[color].green / 65536.0;
2306 gcolor1[2] = mi->colors[color].blue / 65536.0;
2308 gcolor2[0] = gcolor1[0] / 3;
2309 gcolor2[1] = gcolor1[1] / 3;
2310 gcolor2[2] = gcolor1[2] / 3;
2312 glFrontFace(GL_CCW);
2315 glTranslatef(0, 0, 12); /* back of ring in hand */
2317 glTranslatef(x, y, 0);
2318 glScalef(radius, radius, radius);
2320 glRotatef (90, 0, 1, 0);
2321 glRotatef (s->angle / M_PI*180, 0, 0, 1);
2323 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2326 for (j = -1; j <= 1; j += 2)
2328 GLfloat z = j * thickness/2;
2329 glFrontFace (j < 0 ? GL_CCW : GL_CW);
2330 glNormal3f (0, 0, j*1);
2331 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2332 for (i = 0; i < slices + (wire_p ? 0 : 1); i++) {
2333 GLfloat th, cth, sth;
2334 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
2335 (i % (slices/3) ? gcolor1 : gcolor2));
2339 glVertex3f (cth * ra, sth * ra, z);
2340 glVertex3f (cth * rb, sth * rb, z);
2347 glFrontFace (GL_CCW);
2348 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2349 for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2351 GLfloat th = i * width;
2352 GLfloat cth = cos(th);
2353 GLfloat sth = sin(th);
2354 glNormal3f (cth, sth, 0);
2355 glVertex3f (cth * ra, sth * ra, thickness/2);
2356 glVertex3f (cth * ra, sth * ra, -thickness/2);
2362 glFrontFace (GL_CW);
2363 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2364 for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2366 GLfloat th = i * width;
2367 GLfloat cth = cos(th);
2368 GLfloat sth = sin(th);
2369 glNormal3f (-cth, -sth, 0);
2370 glVertex3f (cth * rb, sth * ra, thickness/2);
2371 glVertex3f (cth * rb, sth * ra, -thickness/2);
2376 glFrontFace (GL_CCW);
2383 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2386 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2387 /*int offset = (int)(s->angle*64*180/M_PI);*/
2388 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2389 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2390 double radius = 12 * sp->scale;
2391 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2392 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2396 /* Avoid wrapping */
2397 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2399 gcolor1[0] = mi->colors[color].red / 65536.0;
2400 gcolor1[1] = mi->colors[color].green / 65536.0;
2401 gcolor1[2] = mi->colors[color].blue / 65536.0;
2403 glFrontFace(GL_CCW);
2406 GLfloat scale = radius;
2409 glTranslatef(0, -6, 5); /* position on top of hand */
2411 glTranslatef(x, y, 0);
2412 glScalef(scale, scale, scale);
2413 glRotatef (s->angle / M_PI*180, 1, 0, 1);
2415 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2416 polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2418 glRotatef (90, 0, 0, 1);
2419 glTranslatef (0, 0, 0.81);
2420 glScalef(0.15, 0.15, 0.15);
2421 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2422 for (i = 0; i < 3; i++) {
2424 glTranslatef (0, 0, 1);
2425 glRotatef (360 * i / 3, 0, 0, 1);
2426 glTranslatef (2, 0, 0);
2427 glRotatef (18, 0, 1, 0);
2428 glBegin (MI_IS_WIREFRAME(mi) ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
2429 glVertex3f (0, 0, 0);
2430 for (j = slices; j >= 0; j--) {
2431 GLfloat th = j * M_PI*2 / slices;
2432 glVertex3f (cos(th), sin(th), 0);
2445 /**************************************************************************
2446 * Public Functions *
2448 **************************************************************************/
2452 release_juggle (ModeInfo * mi)
2454 if (juggles != NULL) {
2457 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2458 free_juggle(&juggles[screen]);
2460 juggles = (jugglestruct *) NULL;
2464 /* FIXME: refill_juggle currently just appends new throws to the
2465 * programme. This is fine if the programme is empty, but if there
2466 * are still some trajectories left then it really should take these
2470 refill_juggle(ModeInfo * mi)
2472 jugglestruct *sp = NULL;
2475 if (juggles == NULL)
2477 sp = &juggles[MI_SCREEN(mi)];
2479 /* generate pattern */
2481 if (pattern == NULL) {
2484 #define MAXREPEAT 300
2485 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2486 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2489 while (count < MI_CYCLES(mi)) {
2490 char buf[MAXPAT * 3 + 3], *b = buf;
2492 int l = NRAND(MAXPAT) + 1;
2493 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2495 { /* vary number of balls */
2496 int new_balls = sp->num_balls;
2499 if (new_balls == 2) /* Do not juggle 2 that often */
2500 change = NRAND(2 + CHANGE_BIAS / 4);
2502 change = NRAND(2 + CHANGE_BIAS);
2513 if (new_balls < sp->patternindex.minballs) {
2516 if (new_balls > sp->patternindex.maxballs) {
2519 if (new_balls < sp->num_balls) {
2520 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2523 sp->num_balls = new_balls;
2527 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2528 /* Pick from PortFolio */
2529 int p = sp->patternindex.index[sp->num_balls].start +
2530 NRAND(sp->patternindex.index[sp->num_balls].number);
2531 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2534 /* Invent a new pattern */
2536 for(i = 0; i < l; i++){
2538 do { /* Triangular Distribution => high values more likely */
2539 m = NRAND(sp->num_balls + 1);
2540 n = NRAND(sp->num_balls + 1);
2542 if (n == sp->num_balls) {
2545 switch(NRAND(5 + POSITION_BIAS)){
2546 case 0: /* Outside throw */
2548 case 1: /* Cross throw */
2550 case 2: /* Cross catch */
2552 case 3: /* Cross throw and catch */
2554 case 4: /* Bounce */
2557 break; /* Inside throw (default) */
2566 if (!program(mi, buf, NULL, t))
2571 } else { /* pattern supplied in height or 'a' notation */
2572 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2589 if (!projectile(sp)) {
2596 if(MI_IS_DEBUG(mi)) dump(sp);
2601 change_juggle(ModeInfo * mi)
2603 jugglestruct *sp = NULL;
2606 if (juggles == NULL)
2608 sp = &juggles[MI_SCREEN(mi)];
2610 /* Strip pending trajectories */
2611 for (t = sp->head->next; t != sp->head; t = t->next) {
2612 if(t->start > sp->time || t->finish < sp->time) {
2615 trajectory_destroy(n);
2619 /* Pick the current object theme */
2620 sp->objtypes = choose_object();
2624 mi->polygon_count += show_figure(mi, True);
2629 reshape_juggle (ModeInfo *mi, int width, int height)
2631 GLfloat h = (GLfloat) height / (GLfloat) width;
2633 glViewport (0, 0, (GLint) width, (GLint) height);
2635 glMatrixMode(GL_PROJECTION);
2637 gluPerspective (30.0, 1/h, 1.0, 100.0);
2639 glMatrixMode(GL_MODELVIEW);
2641 gluLookAt( 0.0, 0.0, 30.0,
2645 glClear(GL_COLOR_BUFFER_BIT);
2650 init_juggle (ModeInfo * mi)
2652 jugglestruct *sp = 0;
2653 int wire = MI_IS_WIREFRAME(mi);
2656 juggles = (jugglestruct *)
2657 calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct));
2659 fprintf(stderr, "%s: out of memory\n", progname);
2664 sp = &juggles[MI_SCREEN(mi)];
2666 sp->glx_context = init_GL(mi);
2668 load_font (mi->dpy, "titleFont", &sp->mode_font, &sp->font_dlist);
2670 reshape_juggle (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2671 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
2675 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
2676 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
2677 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
2678 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
2680 glEnable(GL_LIGHTING);
2681 glEnable(GL_LIGHT0);
2682 glEnable(GL_DEPTH_TEST);
2683 glEnable(GL_CULL_FACE);
2685 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2686 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2687 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2688 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2691 make_random_colormap (0, 0, 0,
2692 mi->colors, &MI_NPIXELS(mi),
2693 True, False, 0, False);
2696 double spin_speed = 0.05;
2697 double wander_speed = 0.001;
2698 double spin_accel = 0.05;
2699 sp->rot = make_rotator (0, spin_speed, 0,
2700 spin_accel, wander_speed, False);
2701 sp->trackball = gltrackball_init ();
2704 if (only && *only && strcmp(only, " ")) {
2705 balls = clubs = torches = knives = rings = bballs = False;
2706 if (!strcasecmp (only, "balls")) balls = True;
2707 else if (!strcasecmp (only, "clubs")) clubs = True;
2708 else if (!strcasecmp (only, "torches")) torches = True;
2709 else if (!strcasecmp (only, "knives")) knives = True;
2710 else if (!strcasecmp (only, "rings")) rings = True;
2711 else if (!strcasecmp (only, "bballs")) bballs = True;
2713 (void) fprintf (stderr,
2714 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2715 "\t rings, or bballs (not \"%s\")\n", only);
2716 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2722 /* #### hard to make this look good in OpenGL... */
2726 if (sp->head == 0) { /* first time initializing this juggler */
2728 sp->count = ABS(MI_COUNT(mi));
2732 /* record start time */
2733 sp->begintime = time(NULL);
2734 if(sp->patternindex.maxballs > 0) {
2735 sp->num_balls = sp->patternindex.minballs +
2736 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2739 mi->polygon_count +=
2740 show_figure(mi, True); /* Draw figure. Also discovers
2741 information about the juggler's
2744 /* "7" should be about three times the height of the juggler's
2746 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2747 7 * THROW_CATCH_INTERVAL);
2749 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2750 balls = True; /* Have to juggle something! */
2752 /* create circular trajectory list */
2753 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2754 if(sp->head == NULL){
2759 /* create circular object list */
2760 ADD_ELEMENT(Object, sp->objects, sp->objects);
2761 if(sp->objects == NULL){
2766 sp->pattern = strdup(""); /* Initialise saved pattern with
2770 sp = &juggles[MI_SCREEN(mi)];
2774 !strcasecmp (pattern, ".") ||
2775 !strcasecmp (pattern, "random")))
2778 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2779 /* pattern list needs indexing */
2780 int nelements = countof(portfolio);
2784 /* sort according to number of balls */
2785 qsort((void*)portfolio, nelements,
2786 sizeof(portfolio[1]), compare_num_balls);
2788 /* last pattern has most balls */
2789 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2790 /* run through sorted list, indexing start of each group
2791 and number in group */
2792 sp->patternindex.maxballs = 1;
2793 for (i = 0; i < nelements; i++) {
2794 int b = get_num_balls(portfolio[i].pattern);
2795 if (b > sp->patternindex.maxballs) {
2796 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2797 if(numpat == 0) sp->patternindex.minballs = b;
2798 sp->patternindex.maxballs = b;
2800 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2805 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2808 /* Set up programme */
2811 /* Only put things here that won't interrupt the programme during
2814 /* Use MIN so that users can resize in interesting ways, eg
2815 narrow windows for tall patterns, etc */
2816 sp->scale = MIN(SCENE_HEIGHT/480.0, SCENE_WIDTH/160.0);
2821 juggle_handle_event (ModeInfo *mi, XEvent *event)
2823 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2825 if (event->xany.type == ButtonPress &&
2826 event->xbutton.button == Button1)
2828 sp->button_down_p = True;
2829 gltrackball_start (sp->trackball,
2830 event->xbutton.x, event->xbutton.y,
2831 MI_WIDTH (mi), MI_HEIGHT (mi));
2834 else if (event->xany.type == ButtonRelease &&
2835 event->xbutton.button == Button1)
2837 sp->button_down_p = False;
2840 else if (event->xany.type == ButtonPress &&
2841 (event->xbutton.button == Button4 ||
2842 event->xbutton.button == Button5 ||
2843 event->xbutton.button == Button6 ||
2844 event->xbutton.button == Button7))
2846 gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
2847 !!event->xbutton.state);
2850 else if (event->xany.type == MotionNotify &&
2853 gltrackball_track (sp->trackball,
2854 event->xmotion.x, event->xmotion.y,
2855 MI_WIDTH (mi), MI_HEIGHT (mi));
2858 else if (event->xany.type == KeyPress)
2862 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2863 if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
2876 draw_juggle (ModeInfo *mi)
2878 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2879 Display *dpy = MI_DISPLAY(mi);
2880 Window window = MI_WINDOW(mi);
2882 Trajectory *traj = NULL;
2884 unsigned long future = 0;
2885 char *pattern = NULL;
2887 if (!sp->glx_context)
2890 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
2892 glShadeModel(GL_SMOOTH);
2894 glEnable(GL_DEPTH_TEST);
2895 glEnable(GL_NORMALIZE);
2896 glEnable(GL_CULL_FACE);
2898 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2902 glTranslatef(0,-3,0);
2906 get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
2907 glTranslatef((x - 0.5) * 8,
2911 gltrackball_rotate (sp->trackball);
2913 get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
2915 if (y < 0.8) y = 0.8 - (y - 0.8); /* always face forward */
2916 if (y > 1.2) y = 1.2 - (y - 1.2);
2918 glRotatef (x * 360, 1.0, 0.0, 0.0);
2919 glRotatef (y * 360, 0.0, 1.0, 0.0);
2920 glRotatef (z * 360, 0.0, 0.0, 1.0);
2924 GLfloat scale = 20.0 / SCENE_HEIGHT;
2925 glScalef(scale, scale, scale);
2928 glRotatef (180, 0, 0, 1);
2929 glTranslatef(-SCENE_WIDTH/2, -SCENE_HEIGHT/2, 0);
2930 glTranslatef(0, -150, 0);
2932 mi->polygon_count = 0;
2937 (void)gettimeofday(&tv, NULL);
2938 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2940 sp->time += MI_DELAY(mi) / 1000;
2943 /* First pass: Move arms and strip out expired elements */
2944 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2945 if (traj->status != PREDICTOR) {
2946 /* Skip any elements that need further processing */
2947 /* We could remove them, but there shoudn't be many and they
2948 would be needed if we ever got the pattern refiller
2952 if (traj->start > future) { /* Lookahead to the end of the show */
2953 future = traj->start;
2955 if (sp->time < traj->start) { /* early */
2957 } else if (sp->time < traj->finish) { /* working */
2959 /* Look for pattern name */
2960 if(traj->pattern != NULL) {
2961 pattern=traj->pattern;
2964 if (traj->type == Empty || traj->type == Full) {
2965 /* Only interested in hands on this pass */
2966 /* double angle = traj->angle + traj->spin * (sp->time - traj->start);*/
2967 double xd = 0, yd = 0;
2970 /* Find the catching offset */
2971 if(traj->object != NULL) {
2973 /* #### not sure what this is doing, but I'm guessing
2974 that the use of PERSPEC means this isn't needed
2975 in the OpenGL version? -jwz
2977 if(ObjectDefs[traj->object->type].handle > 0) {
2978 /* Handles Need to be oriented */
2979 xd = ObjectDefs[traj->object->type].handle *
2980 PERSPEC * sin(angle);
2981 yd = ObjectDefs[traj->object->type].handle *
2986 /* Balls are always caught at the bottom */
2991 p.x = (CUBIC(traj->xp, sp->time) - xd);
2992 p.y = (CUBIC(traj->yp, sp->time) + yd);
2993 reach_arm(mi, traj->hand, &p);
2995 /* Store updated hand position */
2999 if (traj->type == Ball || traj->type == Full) {
3000 /* Only interested in objects on this pass */
3004 if(traj->type == Full) {
3005 /* Adjusted these in the first pass */
3009 x = CUBIC(traj->xp, sp->time);
3010 y = CUBIC(traj->yp, sp->time);
3013 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
3016 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
3017 s->divisions = traj->divisions;
3018 traj->object->tracelen++;
3019 traj->object->active = True;
3021 } else { /* expired */
3022 Trajectory *n = traj;
3024 trajectory_destroy(n);
3029 mi->polygon_count += show_figure(mi, False);
3030 mi->polygon_count += show_arms(mi);
3033 glTranslatef(0, 0, ARMLENGTH);
3034 for (o = sp->objects->next; o != sp->objects; o = o->next) {
3036 mi->polygon_count += ObjectDefs[o->type].draw(mi, o->color,
3043 /* Save pattern name so we can erase it when it changes */
3044 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
3046 sp->pattern = strdup(pattern);
3048 if (MI_IS_VERBOSE(mi)) {
3049 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
3050 MI_SCREEN(mi), sp->pattern);
3054 if(sp->mode_font != None) {
3055 print_gl_string (mi->dpy, sp->mode_font, sp->font_dlist,
3056 mi->xgwa.width, mi->xgwa.height,
3057 10, mi->xgwa.height - 10,
3058 sp->pattern, False);
3062 if((int)(sp->time/10) % 1000 == 0)
3063 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
3066 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
3068 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
3075 if (mi->fps_p) do_fps (mi);
3078 glXSwapBuffers(dpy, window);
3081 XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle)