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 # ifdef HAVE_GLBITMAP
584 XFontStruct *mode_font;
587 texture_font_data *font_data;
591 static jugglestruct *juggles = (jugglestruct *) NULL;
597 #define DUP_OBJECT(n, t) { \
598 (n)->object = (t)->object; \
599 if((n)->object != NULL) (n)->object->count++; \
602 /* t must point to an existing element. t must not be an
603 expression ending ->next or ->prev */
604 #define REMOVE(t) { \
605 (t)->next->prev = (t)->prev; \
606 (t)->prev->next = (t)->next; \
610 /* t receives element to be created and added to the list. ot must
611 point to an existing element or be identical to t to start a new
612 list. Applicable to Trajectories, Objects and Traces. */
613 #define ADD_ELEMENT(type, t, ot) \
614 if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
615 (t)->next = (ot)->next; \
618 (t)->next->prev = (t); \
622 object_destroy(Object* o)
624 if(o->trace != NULL) {
625 while(o->trace->next != o->trace) {
626 Trace *s = o->trace->next;
627 REMOVE(s); /* Don't eliminate 's' */
635 trajectory_destroy(Trajectory *t) {
636 if(t->name != NULL) free(t->name);
637 if(t->pattern != NULL) free(t->pattern);
638 /* Reduce object link count and call destructor if necessary */
639 if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
640 object_destroy(t->object);
642 REMOVE(t); /* Unlink and free */
646 free_juggle(jugglestruct *sp) {
647 if (sp->head != NULL) {
648 while (sp->head->next != sp->head) {
649 trajectory_destroy(sp->head->next);
652 sp->head = (Trajectory *) NULL;
654 if(sp->objects != NULL) {
655 while (sp->objects->next != sp->objects) {
656 object_destroy(sp->objects->next);
659 sp->objects = (Object*)NULL;
661 if(sp->pattern != NULL) {
665 # ifdef HAVE_GLBITMAP
666 if (sp->mode_font!=None) {
667 XFreeFontInfo(NULL,sp->mode_font,1);
668 sp->mode_font = None;
670 # endif /* HAVE_GLBITMAP */
674 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
678 ADD_ELEMENT(Trajectory, t, sp->head->prev);
679 if(t == NULL){ /* Out of Memory */
685 t->name = strdup(name);
698 /* add a Thratch to the performance */
700 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
702 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
708 if (MI_IS_VERBOSE(mi)) {
709 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
710 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
713 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
714 have been programmed */
715 /* title is the pattern name to be supplied to the first throw of
716 a sequence. If no name if given, use an empty title so that
717 the sequences are still delimited. */
718 const char *title = (name != NULL)? name : "";
723 for(p=patn; *p; p++) {
724 if (*p >= '0' && *p <='9') {
726 h = 10*h + (*p - '0');
728 Notation nn = notation;
730 case '[': /* begin Adam notation */
733 case '-': /* Inside throw */
736 case '+': /* Outside throw */
737 case '=': /* Cross throw */
738 case '&': /* Cross catch */
739 case 'x': /* Cross throw and catch */
740 case '_': /* Bounce */
741 case 'k': /* Kickup */
744 case '*': /* Lose ball */
748 case ']': /* end Adam notation */
754 if (!add_throw(sp, type, h, notation, title))
764 if(w == 0) { /* Only warn on first pass */
765 (void) fprintf(stderr,
766 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
773 if (seen) { /* end of sequence */
774 if (!add_throw(sp, type, h, notation, title))
788 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
790 4 4 1 3 12 2 4 1 4 4 13 0 3 1
793 #define BOUNCEOVER 10
797 /* Convert Adam notation into heights */
799 adam(jugglestruct *sp)
802 for(t = sp->head->next; t != sp->head; t = t->next) {
803 if (t->status == ATCH) {
806 for(p = t->next; a > 0; p = p->next) {
808 t->height = -9; /* Indicate end of processing for name() */
811 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
816 if(t->height > BOUNCEOVER && t->posn == ' '){
817 t->posn = '_'; /* high defaults can be bounced */
818 } else if(t->height < 3 && t->posn == '_') {
819 t->posn = ' '; /* Can't bounce short throws. */
821 if(t->height < KICKMIN && t->posn == 'k'){
822 t->posn = ' '; /* Can't kick short throws */
824 if(t->height > THROWMAX){
825 t->posn = 'k'; /* Use kicks for ridiculously high throws */
832 /* Discover converted heights and update the sequence title */
834 name(jugglestruct *sp)
839 for(t = sp->head->next; t != sp->head; t = t->next) {
840 if (t->status == THRATCH && t->name != NULL) {
842 for(p = t; p == t || p->name == NULL; p = p->next) {
843 if(p == sp->head || p->height < 0) { /* end of reliable data */
847 b += sprintf(b, " %d", p->height);
849 b += sprintf(b, " %c%d", p->posn, p->height);
851 if(b - buffer > 500) break; /* otherwise this could eventually
852 overflow. It'll be too big to
856 (void) sprintf(b, ", %s", t->name);
858 free(t->name); /* Don't need name any more, it's been converted
861 if(t->pattern != NULL) free(t->pattern);
862 t->pattern = strdup(buffer);
867 /* Split Thratch notation into explicit throws and catches.
868 Usually Catch follows Throw in same hand, but take care of special
871 /* ..n1.. -> .. LTn RT1 LC RC .. */
872 /* ..nm.. -> .. LTn LC RTm RC .. */
875 part(jugglestruct *sp)
877 Trajectory *t, *nt, *p;
878 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
880 for (t = sp->head->next; t != sp->head; t = t->next) {
881 if (t->status > THRATCH) {
883 } else if (t->status == THRATCH) {
886 /* plausibility check */
887 if (t->height <= 2 && t->posn == '_') {
888 t->posn = ' '; /* no short bounces */
890 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
891 t->posn = ' '; /* 1's need close catches */
896 case ' ': posn = '-'; t->posn = '+'; break;
897 case '+': posn = '+'; t->posn = '-'; break;
898 case '=': posn = '='; t->posn = '+'; break;
899 case '&': posn = '+'; t->posn = '='; break;
900 case 'x': posn = '='; t->posn = '='; break;
901 case '_': posn = '_'; t->posn = '-'; break;
902 case 'k': posn = 'k'; t->posn = 'k'; break;
904 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
907 hand = (Hand) ((hand + 1) % 2);
912 if (t->height == 1 && p != sp->head) {
913 p = p->prev; /* '1's are thrown earlier than usual */
919 ADD_ELEMENT(Trajectory, nt, p);
927 nt->height = t->height;
937 choose_object(void) {
940 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
941 if(balls && o == BALL) break;
942 if(clubs && o == CLUB) break;
943 if(torches && o == TORCH) break;
944 if(knives && o == KNIFE) break;
945 if(rings && o == RING) break;
946 if(bballs && o == BBALLS) break;
951 /* Connnect up throws and catches to figure out which ball goes where.
952 Do the same with the juggler's hands. */
957 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
960 for (t = sp->head->next; t != sp->head; t = t->next) {
961 if (t->status == ACTION) {
962 if (t->action == THROW) {
963 if (t->type == Empty) {
964 /* Create new Object */
965 ADD_ELEMENT(Object, t->object, sp->objects);
966 t->object->count = 1;
967 t->object->tracelen = 0;
968 t->object->active = False;
969 /* Initialise object's circular trace list */
970 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
972 if (MI_NPIXELS(mi) > 2) {
973 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
976 t->object->color = 1;
978 t->object->color = 0;
982 /* Small chance of picking a random object instead of the
984 if(NRAND(OBJMIXPROB) == 0) {
985 t->object->type = choose_object();
987 t->object->type = sp->objtypes;
990 /* Check to see if we need trails for this object */
991 if(tail < ObjectDefs[t->object->type].mintrail) {
992 t->object->tail = ObjectDefs[t->object->type].mintrail;
994 t->object->tail = tail;
998 /* Balls can change divisions at each throw */
999 /* no, that looks stupid. -jwz */
1000 if (t->divisions < 1)
1001 t->divisions = 2 * (NRAND(2) + 1);
1003 /* search forward for next catch in this hand */
1004 for (p = t->next; t->handlink == NULL; p = p->next) {
1005 if(p->status < ACTION || p == sp->head) return;
1006 if (p->action == CATCH) {
1007 if (t->handlink == NULL && p->hand == t->hand) {
1013 if (t->height > 0) {
1016 /* search forward for next ball catch */
1017 for (p = t->next; t->balllink == NULL; p = p->next) {
1018 if(p->status < ACTION || p == sp->head) {
1022 if (p->action == CATCH) {
1023 if (t->balllink == NULL && --h < 1) { /* caught */
1024 t->balllink = p; /* complete trajectory */
1026 if (p->type == Full) {
1027 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1028 MI_SCREEN(mi), t->object->color);
1032 DUP_OBJECT(p, t); /* accept catch */
1033 p->angle = t->angle;
1034 p->divisions = t->divisions;
1039 t->type = Empty; /* thrown */
1040 } else if (t->action == CATCH) {
1041 /* search forward for next throw from this hand */
1042 for (p = t->next; t->handlink == NULL; p = p->next) {
1043 if(p->status < ACTION || p == sp->head) return;
1044 if (p->action == THROW && p->hand == t->hand) {
1045 p->type = t->type; /* pass ball */
1046 DUP_OBJECT(p, t); /* pass object */
1047 p->divisions = t->divisions;
1052 t->status = LINKEDACTION;
1057 /* Clap when both hands are empty */
1059 clap(jugglestruct *sp)
1062 for (t = sp->head->next; t != sp->head; t = t->next) {
1063 if (t->status == LINKEDACTION &&
1064 t->action == CATCH &&
1066 t->handlink != NULL &&
1067 t->handlink->height == 0) { /* Completely idle hand */
1069 for (p = t->next; p != sp->head; p = p->next) {
1070 if (p->status == LINKEDACTION &&
1071 p->action == CATCH &&
1072 p->hand != t->hand) { /* Next catch other hand */
1073 if(p->type == Empty &&
1074 p->handlink != NULL &&
1075 p->handlink->height == 0) { /* Also completely idle */
1077 t->handlink->posn = '^'; /* Move first hand's empty throw */
1078 p->posn = '^'; /* to meet second hand's empty
1082 break; /* Only need first catch */
1089 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1091 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1092 with velocity dx1 at time t1 */
1094 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1103 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1104 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1109 s.c = (3*a*t0 - 2*b)*t0 + c;
1110 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1114 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1115 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1116 velocity dx2 at time t2. The arrival and departure velocities at
1117 x1, t1 must be the same. */
1119 makeSplinePair(Spline *s1, Spline *s2,
1120 double x0, double dx0, int t0,
1122 double x2, double dx2, int t2)
1124 double x10, x21, t21, t10, t20, dx1;
1130 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1131 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1133 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1134 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1138 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1139 from x at time t at constant velocity dx. sy goes from y at time t
1140 with velocity dy and constant acceleration g. */
1142 makeParabola(Trajectory *n,
1143 double x, double dx, double y, double dy, double g)
1145 double t = (double)n->start;
1149 n->xp.d = -dx*t + x;
1152 n->yp.c = -g*t + dy;
1153 n->yp.d = g/2*t*t - dy*t + y;
1159 #define SX 25 /* Shoulder Width */
1161 /* Convert hand position symbols into actual time/space coordinates */
1163 positions(jugglestruct *sp)
1166 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1167 for (t = sp->head->next; t != sp->head; t = t->next) {
1168 if (t->status >= PTHRATCH) {
1170 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1171 /* Allow ACTIONs to be annotated, but we won't mark them ready
1172 for the next stage */
1179 if (t->action == CATCH) { /* Throw-to-catch */
1180 if (t->type == Empty) {
1181 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1182 } else { /* successful catch */
1183 now += (int)(THROW_CATCH_INTERVAL);
1185 } else { /* Catch-to-throw */
1186 if(t->object != NULL) {
1187 now += (int) (CATCH_THROW_INTERVAL *
1188 ObjectDefs[t->object->type].weight);
1190 now += (int) (CATCH_THROW_INTERVAL);
1196 else /* Concatenated performances may need clock resync */
1204 /* Add room for the handle */
1205 if(t->action == CATCH && t->object != NULL)
1206 yo -= ObjectDefs[t->object->type].handle;
1209 case '-': xo = sx - pose; break;
1212 case '+': xo = sx + pose; break;
1214 case '=': xo = - sx - pose; yo += pose; break;
1215 case '^': xo = 0; yo += pose*2; break; /* clap */
1217 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1221 #ifdef _2DSpinsDontWorkIn3D
1222 t->angle = (((t->hand == LEFT) ^
1223 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1229 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1232 /* Only mark complete if it was already linked */
1233 if(t->status == LINKEDACTION) {
1234 t->status = PTHRATCH;
1241 /* Private physics functions */
1243 /* Compute the spin-rate for a trajectory. Different types of throw
1244 (eg, regular thows, bounces, kicks, etc) have different spin
1247 type = type of object
1248 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1249 old = earlier spin to consider
1250 dt = time span of this trajectory
1251 height = height of ball throw or 0 if based on old spin
1252 turns = full club turns required during this operation
1253 togo = partial club turns required to match hands
1256 spinrate(ObjType type, Trajectory *h, double old, double dt,
1257 int height, int turns, double togo)
1259 #ifdef _2DSpinsDontWorkIn3D
1260 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1265 if(ObjectDefs[type].handle != 0) { /* Clubs */
1266 return (dir * turns * 2 * M_PI + togo) / dt;
1267 } else if(height == 0) { /* Balls already spinning */
1269 } else { /* Balls */
1270 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1275 /* compute the angle at the end of a spinning trajectory */
1277 end_spin(Trajectory *t)
1279 return t->angle + t->spin * (t->finish - t->start);
1282 /* Sets the initial angle of the catch following hand movement t to
1283 the final angle of the throw n. Also sets the angle of the
1284 subsequent throw to the same angle plus half a turn. */
1286 match_spins_on_catch(Trajectory *t, Trajectory *n)
1288 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1289 t->balllink->angle = end_spin(n);
1290 if(t->balllink->handlink != NULL) {
1291 #ifdef _2DSpinsDontWorkIn3D
1292 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1294 t->balllink->handlink->angle = t->balllink->angle;
1301 find_bounce(jugglestruct *sp,
1302 double yo, double yf, double yc, double tc, double cor)
1304 double tb, i, dy = 0;
1305 const double e = 1; /* permissible error in yc */
1309 yt = height at catch time after one bounce
1310 one or three roots according to timing
1311 find one by interval bisection
1314 for(i = tc / 2; i > 0.0001; i/=2){
1317 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1320 dy = (yf - yo)/tb + sp->Gr/2*tb;
1322 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1325 }else if(yt > yc - e){
1331 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1338 new_predictor(const Trajectory *t, int start, int finish, double angle)
1341 ADD_ELEMENT(Trajectory, n, t->prev);
1346 n->divisions = t->divisions;
1348 n->status = PREDICTOR;
1356 /* Turn abstract timings into physically appropriate object trajectories. */
1358 projectile(jugglestruct *sp)
1361 const int yf = 0; /* Floor height */
1363 for (t = sp->head->next; t != sp->head; t = t->next) {
1364 if (t->status != PTHRATCH || t->action != THROW) {
1366 } else if (t->balllink == NULL) { /* Zero Throw */
1367 t->status = BPREDICTOR;
1368 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1370 } else if(t->balllink == t->handlink) {
1371 /* '2' height - hold on to ball. Don't need to consider
1372 flourishes, 'hands' will do that automatically anyway */
1375 /* Zero spin to avoid wrist injuries */
1377 match_spins_on_catch(t, t);
1379 t->status = BPREDICTOR;
1382 if (t->posn == '_') { /* Bounce once */
1384 const int tb = t->start +
1385 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1386 (double) (t->balllink->start - t->start),
1387 ObjectDefs[t->object->type].cor);
1389 if(tb < t->start) { /* bounce too hard */
1390 t->posn = '+'; /* Use regular throw */
1392 Trajectory *n; /* First (throw) trajectory. */
1393 double dt; /* Time span of a trajectory */
1394 double dy; /* Distance span of a follow-on trajectory.
1395 First trajectory uses t->dy */
1396 /* dx is constant across both trajectories */
1397 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1399 { /* ball follows parabola down */
1400 n = new_predictor(t, t->start, tb, t->angle);
1401 if(n == NULL) return False;
1402 dt = n->finish - n->start;
1403 /* Ball rate 4, no flight or matching club turns */
1404 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1405 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1406 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1409 { /* ball follows parabola up */
1410 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1412 if(m == NULL) return False;
1413 dt = m->finish - m->start;
1414 /* Use previous ball rate, no flight club turns */
1415 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1416 t->balllink->angle - m->angle);
1417 match_spins_on_catch(t, m);
1418 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1419 makeParabola(m, t->balllink->x - t->dx * dt,
1420 t->dx, (double) yf, dy, sp->Gr);
1423 t->status = BPREDICTOR;
1426 } else if (t->posn == 'k') { /* Drop & Kick */
1427 Trajectory *n; /* First (drop) trajectory. */
1428 Trajectory *o; /* Second (rest) trajectory */
1429 Trajectory *m; /* Third (kick) trajectory */
1430 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1431 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1434 { /* Fall to ground */
1435 n = new_predictor(t, t->start, td, t->angle);
1436 if(n == NULL) return False;
1437 dt = n->finish - n->start;
1438 /* Ball spin rate 4, no flight club turns */
1439 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1440 t->balllink->angle - n->angle);
1441 t->dx = (t->balllink->x - t->x) / dt;
1442 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1443 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1446 { /* Rest on ground */
1447 o = new_predictor(t, n->finish, tk, end_spin(n));
1448 if(o == NULL) return False;
1450 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1455 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1456 if(m == NULL) return False;
1457 dt = m->finish - m->start;
1458 /* Match receiving hand, ball rate 4, one flight club turn */
1459 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1460 4, 1, t->balllink->angle - m->angle);
1461 match_spins_on_catch(t, m);
1462 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1463 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1466 t->status = BPREDICTOR;
1470 /* Regular flight, no bounce */
1471 { /* ball follows parabola */
1473 Trajectory *n = new_predictor(t, t->start,
1474 t->balllink->start, t->angle);
1475 if(n == NULL) return False;
1476 dt = t->balllink->start - t->start;
1478 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1479 t->balllink->angle - n->angle);
1480 match_spins_on_catch(t, n);
1481 t->dx = (t->balllink->x - t->x) / dt;
1482 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1483 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1486 t->status = BPREDICTOR;
1492 /* Turn abstract hand motions into cubic splines. */
1494 hands(jugglestruct *sp)
1496 Trajectory *t, *u, *v;
1498 for (t = sp->head->next; t != sp->head; t = t->next) {
1499 /* no throw => no velocity */
1500 if (t->status != BPREDICTOR) {
1505 if (u == NULL) { /* no next catch */
1509 if (v == NULL) { /* no next throw */
1513 /* double spline takes hand from throw, thru catch, to
1516 t->finish = u->start;
1517 t->status = PREDICTOR;
1519 u->finish = v->start;
1520 u->status = PREDICTOR;
1523 /* FIXME: These adjustments leave a small glitch when alternating
1524 balls and clubs. Just hope no-one notices. :-) */
1526 /* make sure empty hand spin matches the thrown object in case it
1529 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1530 fabs((u->angle - t->angle)/(u->start - t->start));
1532 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1533 fabs((v->angle - u->angle)/(v->start - u->start));
1535 (void) makeSplinePair(&t->xp, &u->xp,
1536 t->x, t->dx, t->start,
1538 v->x, v->dx, v->start);
1539 (void) makeSplinePair(&t->yp, &u->yp,
1540 t->y, t->dy, t->start,
1542 v->y, v->dy, v->start);
1544 t->status = PREDICTOR;
1548 /* Given target x, y find_elbow puts hand at target if possible,
1549 * otherwise makes hand point to the target */
1551 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1555 double x = p->x - s->x;
1556 double y = p->y - s->y;
1557 h2 = x*x + y*y + z*z;
1558 if (h2 > 4 * armlength * armlength) {
1559 t = armlength/sqrt(h2);
1562 h->x = 2 * t * x + s->x;
1563 h->y = 2 * t * y + s->y;
1565 r = sqrt((double)(x*x + z*z));
1566 t = sqrt(4 * armlength * armlength / h2 - 1);
1567 e->x = x*(1 + y*t/r)/2 + s->x;
1568 e->y = (y - r*t)/2 + s->y;
1575 /* NOTE: returned x, y adjusted for arm reach */
1577 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1579 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1581 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1582 *p = sp->arm[1][side][HAND] = h;
1583 sp->arm[1][side][ELBOW] = e;
1587 /* dumps a human-readable rendition of the current state of the juggle
1588 pipeline to stderr for debugging */
1590 dump(jugglestruct *sp)
1593 for (t = sp->head->next; t != sp->head; t = t->next) {
1594 switch (t->status) {
1596 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1599 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1600 t->pattern == NULL?"":t->pattern);
1603 if (t->action == CATCH)
1604 (void) fprintf(stderr, "%p A %c%cC\n",
1606 t->hand ? 'R' : 'L');
1608 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1610 t->hand ? 'R' : 'L',
1611 (t->action == THROW)?'T':'N',
1615 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1618 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1619 t->height, t->object == NULL?0:t->object->color,
1620 (void*)t->handlink, (void*)t->balllink);
1623 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1626 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1627 t->height, t->type, t->object == NULL?0:t->object->color,
1628 t->start, t->finish);
1631 (void) fprintf(stderr, "%p B %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 P %c %2d %6lu %6lu %g\n",
1638 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1639 t->object == NULL?0:t->object->color,
1640 t->start, t->finish, t->yp.c);
1643 (void) fprintf(stderr, "%p: status %d not implemented\n",
1644 (void*)t, t->status);
1648 (void) fprintf(stderr, "---\n");
1652 static int get_num_balls(const char *j)
1658 for (p = j; *p; p++) {
1659 if (*p >= '0' && *p <='9') { /* digit */
1660 h = 10*h + (*p - '0');
1672 compare_num_balls(const void *p1, const void *p2)
1675 i = get_num_balls(((patternstruct*)p1)->pattern);
1676 j = get_num_balls(((patternstruct*)p2)->pattern);
1687 /**************************************************************************
1688 * Rendering Functions *
1690 **************************************************************************/
1693 show_arms(ModeInfo * mi)
1696 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1699 XPoint a[countof(sp->arm[0][0])];
1705 glFrontFace(GL_CCW);
1708 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1709 /* Translate into device coords */
1710 for(i = 0; i < countof(a); i++) {
1711 a[i].x = (short)(SCENE_WIDTH/2 + sp->arm[j][side][i].x*sp->scale);
1712 a[i].y = (short)(SCENE_HEIGHT - sp->arm[j][side][i].y*sp->scale);
1714 sp->arm[0][side][i] = sp->arm[1][side][i];
1717 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1720 polys += tube (a[2].x - (side == LEFT ? soffx : -soffx), a[2].y + soffy, 0,
1721 a[1].x, a[1].y, ARMLENGTH/2,
1722 thickness, 0, slices,
1723 True, True, MI_IS_WIREFRAME(mi));
1726 polys += tube (a[1].x, a[1].y, ARMLENGTH/2,
1727 a[0].x, a[0].y, ARMLENGTH,
1728 thickness * 0.8, 0, slices,
1729 True, True, MI_IS_WIREFRAME(mi));
1731 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1735 glTranslatef (a[2].x - (side == LEFT ? soffx : -soffx),
1739 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1744 glTranslatef (a[1].x, a[1].y, ARMLENGTH/2);
1746 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1751 glTranslatef (a[0].x, a[0].y, ARMLENGTH);
1753 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1761 show_figure(ModeInfo * mi, Bool init)
1764 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1785 /* #### most of this is unused now */
1786 static const XPoint figure[] = {
1787 { 15, 70}, /* 0 Left Hip */
1788 { 0, 90}, /* 1 Waist */
1789 { SX, 130}, /* 2 Left Shoulder */
1790 {-SX, 130}, /* 3 Right Shoulder */
1791 {-15, 70}, /* 4 Right Hip */
1792 { 0, 130}, /* 5 Neck */
1793 { 0, 140}, /* 6 Chin */
1794 { SX, 0}, /* 7 Left Foot */
1795 {-SX, 0}, /* 8 Right Foot */
1796 {-17, 174}, /* 9 Head1 */
1797 { 17, 140}, /* 10 Head2 */
1799 XPoint a[countof(figure)];
1800 GLfloat gcolor[4] = { 1, 1, 1, 1 };
1802 /* Translate into device coords */
1803 for(i = 0; i < countof(figure); i++) {
1804 a[i].x = (short)(SCENE_WIDTH/2 + (sp->cx + figure[i].x)*sp->scale);
1805 a[i].y = (short)(SCENE_HEIGHT - figure[i].y*sp->scale);
1808 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor);
1810 glFrontFace(GL_CCW);
1813 GLfloat scale = ((GLfloat) a[10].x - a[9].x) / 2;
1818 glTranslatef(a[6].x, a[6].y - scale, 0);
1819 glScalef(scale, scale, scale);
1822 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1825 glScalef(scale, scale, scale);
1826 glTranslatef(0, 0.3, 0);
1828 glTranslatef(0, 0, 0.35);
1829 polys += tube (0, 0, 0,
1832 slices, True, True, MI_IS_WIREFRAME(mi));
1834 glScalef(0.9, 0.9, 1);
1835 polys += unit_sphere(2*slices, 2*slices, MI_IS_WIREFRAME(mi));
1839 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1840 glTranslatef(0, 1.1, 0);
1843 glScalef(scale, scale, scale);
1844 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1848 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1849 glTranslatef(0, 1.1, 0);
1851 glScalef(0.9, 1.0, 0.9);
1852 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1856 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1857 glTranslatef(0, 1.0, 0);
1860 glScalef(scale, scale, scale);
1861 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1865 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1866 glTranslatef(0, 0.8, 0);
1869 glScalef(scale, scale, scale);
1870 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1875 glTranslatef(0, 0.7, 0);
1877 for (i = -1; i <= 1; i += 2) {
1880 glRotatef (i*10, 0, 0, 1);
1881 glTranslatef(-i*0.65, 0, 0);
1884 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1886 glScalef(scale, scale, scale);
1887 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1890 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1892 glTranslatef(0, 0.6, 0);
1893 polys += tube (0, 0, 0,
1896 slices, True, True, MI_IS_WIREFRAME(mi));
1900 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1902 glTranslatef(0, 4.4, 0);
1904 glScalef(scale, scale, scale);
1905 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1909 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1911 glTranslatef(0, 4.7, 0);
1912 polys += tube (0, 0, 0,
1915 slices, True, True, MI_IS_WIREFRAME(mi));
1919 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_2);
1921 glTranslatef(0, 9.7, 0);
1923 glScalef(scale, scale, scale);
1924 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
1928 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, body_color_1);
1930 glRotatef (-i*10, 0, 0, 1);
1931 glTranslatef(-i*1.75, 9.7, 0.9);
1933 glScalef (0.4, 1, 1);
1934 polys += tube (0, 0, 0,
1937 slices*4, True, True, MI_IS_WIREFRAME(mi));
1946 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1947 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1949 /* Initialise arms */
1951 for(i = 0; i < 2; i++){
1952 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1953 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1954 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1955 sp->arm[i][LEFT][HAND].x = figure[0].x;
1956 sp->arm[i][LEFT][HAND].y = figure[1].y;
1957 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1958 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1959 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1960 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1961 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1967 typedef struct { GLfloat x, y, z; } XYZ;
1969 /* lifted from sphere.c */
1971 striped_unit_sphere (int stacks, int slices,
1973 GLfloat *color1, GLfloat *color2,
1978 double theta1, theta2, theta3;
1980 XYZ la = { 0, 0, 0 }, lb = { 0, 0, 0 };
1981 XYZ c = {0, 0, 0}; /* center */
1982 double r = 1.0; /* radius */
1983 int stacks2 = stacks * 2;
1990 if (slices < 4 || stacks < 2 || r <= 0)
1992 glBegin (GL_POINTS);
1993 glVertex3f (c.x, c.y, c.z);
2000 for (j = 0; j < stacks; j++)
2002 theta1 = j * (M_PI+M_PI) / stacks2 - M_PI_2;
2003 theta2 = (j + 1) * (M_PI+M_PI) / stacks2 - M_PI_2;
2005 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
2006 ((j == 0 || j == stacks-1 ||
2007 j % (stacks / (stripes+1)))
2008 ? color1 : color2));
2010 glBegin (wire_p ? GL_LINE_LOOP : GL_TRIANGLE_STRIP);
2011 for (i = 0; i <= slices; i++)
2013 theta3 = i * (M_PI+M_PI) / slices;
2015 if (wire_p && i != 0)
2017 glVertex3f (lb.x, lb.y, lb.z);
2018 glVertex3f (la.x, la.y, la.z);
2021 e.x = cos (theta2) * cos(theta3);
2023 e.z = cos (theta2) * sin(theta3);
2024 p.x = c.x + r * e.x;
2025 p.y = c.y + r * e.y;
2026 p.z = c.z + r * e.z;
2028 glNormal3f (e.x, e.y, e.z);
2029 glTexCoord2f (i / (double)slices,
2030 2*(j+1) / (double)stacks2);
2031 glVertex3f (p.x, p.y, p.z);
2034 e.x = cos(theta1) * cos(theta3);
2036 e.z = cos(theta1) * sin(theta3);
2037 p.x = c.x + r * e.x;
2038 p.y = c.y + r * e.y;
2039 p.z = c.z + r * e.z;
2041 glNormal3f (e.x, e.y, e.z);
2042 glTexCoord2f (i / (double)slices,
2043 2*j / (double)stacks2);
2044 glVertex3f (p.x, p.y, p.z);
2056 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
2059 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2060 /*int offset = (int)(s->angle*64*180/M_PI);*/
2061 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2062 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2063 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2064 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2067 /* Avoid wrapping */
2068 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2070 gcolor1[0] = mi->colors[color].red / 65536.0;
2071 gcolor1[1] = mi->colors[color].green / 65536.0;
2072 gcolor1[2] = mi->colors[color].blue / 65536.0;
2074 gcolor2[0] = gcolor1[0] / 3;
2075 gcolor2[1] = gcolor1[1] / 3;
2076 gcolor2[2] = gcolor1[2] / 3;
2078 glFrontFace(GL_CCW);
2081 GLfloat scale = BALLRADIUS;
2083 glTranslatef(x, y, 0);
2084 glScalef(scale, scale, scale);
2086 glRotatef (s->angle / M_PI*180, 1, 1, 0);
2088 polys += striped_unit_sphere (slices, slices, s->divisions,
2089 gcolor1, gcolor2, MI_IS_WIREFRAME(mi));
2096 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
2099 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2100 /*int offset = (int)(s->angle*64*180/M_PI);*/
2101 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2102 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2103 double radius = 12 * sp->scale;
2104 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2105 GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2107 int divs = s->divisions;
2128 /* Avoid wrapping */
2129 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2131 gcolor1[0] = mi->colors[color].red / 65536.0;
2132 gcolor1[1] = mi->colors[color].green / 65536.0;
2133 gcolor1[2] = mi->colors[color].blue / 65536.0;
2135 glFrontFace(GL_CCW);
2138 GLfloat scale = radius;
2140 glTranslatef(x, y, 0);
2141 glScalef(scale, scale, scale);
2143 glTranslatef (0, 0, 2); /* put end of handle in hand */
2145 glRotatef (s->angle / M_PI*180, 1, 0, 0);
2148 glScalef (0.5, 1, 0.5);
2149 polys += striped_unit_sphere (slices, slices, divs, gcolor2, gcolor1,
2150 MI_IS_WIREFRAME(mi));
2152 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2153 polys += tube (0, 0, 0,
2156 slices, True, True, MI_IS_WIREFRAME(mi));
2158 glTranslatef (0, 2, 0);
2159 glScalef (0.25, 0.25, 0.25);
2160 polys += unit_sphere(slices, slices, MI_IS_WIREFRAME(mi));
2169 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2173 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2174 XPoint head, tail, last;
2175 DXPoint dhead, dlast;
2176 const double sa = sin(s->angle);
2177 const double ca = cos(s->angle);
2179 const double TailLen = -24;
2180 const double HeadLen = 16;
2181 const short Width = (short)(5 * sqrt(sp->scale));
2193 dhead.x = s->x + HeadLen * PERSPEC * sa;
2194 dhead.y = s->y - HeadLen * ca;
2196 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2198 } else { /* Store 'last' so we can use it later when s->prev has
2200 if(s->prev != s->next) {
2201 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2202 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2209 /* Avoid wrapping (after last is stored) */
2210 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2212 head.x = (short)(SCENE_WIDTH/2 + dhead.x*sp->scale);
2213 head.y = (short)(SCENE_HEIGHT - dhead.y*sp->scale);
2215 last.x = (short)(SCENE_WIDTH/2 + dlast.x*sp->scale);
2216 last.y = (short)(SCENE_HEIGHT - dlast.y*sp->scale);
2218 tail.x = (short)(SCENE_WIDTH/2 +
2219 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2220 tail.y = (short)(SCENE_HEIGHT - (s->y - TailLen * ca)*sp->scale );
2222 if(color != MI_BLACK_PIXEL(mi)) {
2223 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2224 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2225 Width, LineSolid, CapRound, JoinRound);
2226 draw_line(mi, head.x, head.y, tail.x, tail.y);
2228 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2229 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2230 Width * 2, LineSolid, CapRound, JoinRound);
2232 draw_line(mi, head.x, head.y, last.x, last.y);
2240 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2243 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2244 /*int offset = (int)(s->angle*64*180/M_PI);*/
2245 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2246 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2247 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2248 GLfloat gcolor2[4] = { 1, 1, 1, 1 };
2251 /* Avoid wrapping */
2252 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2254 gcolor1[0] = mi->colors[color].red / 65536.0;
2255 gcolor1[1] = mi->colors[color].green / 65536.0;
2256 gcolor1[2] = mi->colors[color].blue / 65536.0;
2258 glFrontFace(GL_CCW);
2261 glTranslatef(x, y, 0);
2264 glTranslatef (0, 0, 2); /* put end of handle in hand */
2265 glRotatef (s->angle / M_PI*180, 1, 0, 0);
2267 glScalef (0.3, 1, 1); /* flatten blade */
2269 glTranslatef(0, 6, 0);
2270 glRotatef (180, 1, 0, 0);
2272 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2273 polys += tube (0, 0, 0,
2276 slices, True, True, MI_IS_WIREFRAME(mi));
2278 glTranslatef (0, 12, 0);
2279 glScalef (0.7, 10, 0.7);
2280 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2281 polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2289 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2292 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2293 /*int offset = (int)(s->angle*64*180/M_PI);*/
2294 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2295 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2296 double radius = 12 * sp->scale;
2297 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2298 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2301 int wire_p = MI_IS_WIREFRAME(mi);
2302 GLfloat width = M_PI * 2 / slices;
2305 GLfloat thickness = 0.15;
2307 /* Avoid wrapping */
2308 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2310 gcolor1[0] = mi->colors[color].red / 65536.0;
2311 gcolor1[1] = mi->colors[color].green / 65536.0;
2312 gcolor1[2] = mi->colors[color].blue / 65536.0;
2314 gcolor2[0] = gcolor1[0] / 3;
2315 gcolor2[1] = gcolor1[1] / 3;
2316 gcolor2[2] = gcolor1[2] / 3;
2318 glFrontFace(GL_CCW);
2321 glTranslatef(0, 0, 12); /* back of ring in hand */
2323 glTranslatef(x, y, 0);
2324 glScalef(radius, radius, radius);
2326 glRotatef (90, 0, 1, 0);
2327 glRotatef (s->angle / M_PI*180, 0, 0, 1);
2329 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2332 for (j = -1; j <= 1; j += 2)
2334 GLfloat z = j * thickness/2;
2335 glFrontFace (j < 0 ? GL_CCW : GL_CW);
2336 glNormal3f (0, 0, j*1);
2337 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2338 for (i = 0; i < slices + (wire_p ? 0 : 1); i++) {
2339 GLfloat th, cth, sth;
2340 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
2341 (i % (slices/3) ? gcolor1 : gcolor2));
2345 glVertex3f (cth * ra, sth * ra, z);
2346 glVertex3f (cth * rb, sth * rb, z);
2353 glFrontFace (GL_CCW);
2354 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2355 for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2357 GLfloat th = i * width;
2358 GLfloat cth = cos(th);
2359 GLfloat sth = sin(th);
2360 glNormal3f (cth, sth, 0);
2361 glVertex3f (cth * ra, sth * ra, thickness/2);
2362 glVertex3f (cth * ra, sth * ra, -thickness/2);
2368 glFrontFace (GL_CW);
2369 glBegin (wire_p ? GL_LINES : GL_QUAD_STRIP);
2370 for (i = 0; i < slices + (wire_p ? 0 : 1); i++)
2372 GLfloat th = i * width;
2373 GLfloat cth = cos(th);
2374 GLfloat sth = sin(th);
2375 glNormal3f (-cth, -sth, 0);
2376 glVertex3f (cth * rb, sth * ra, thickness/2);
2377 glVertex3f (cth * rb, sth * ra, -thickness/2);
2382 glFrontFace (GL_CCW);
2389 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2392 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2393 /*int offset = (int)(s->angle*64*180/M_PI);*/
2394 short x = (short)(SCENE_WIDTH/2 + s->x * sp->scale);
2395 short y = (short)(SCENE_HEIGHT - s->y * sp->scale);
2396 double radius = 12 * sp->scale;
2397 GLfloat gcolor1[4] = { 0, 0, 0, 1 };
2398 GLfloat gcolor2[4] = { 0, 0, 0, 1 };
2402 /* Avoid wrapping */
2403 if(s->y*sp->scale > SCENE_HEIGHT * 2) return 0;
2405 gcolor1[0] = mi->colors[color].red / 65536.0;
2406 gcolor1[1] = mi->colors[color].green / 65536.0;
2407 gcolor1[2] = mi->colors[color].blue / 65536.0;
2409 glFrontFace(GL_CCW);
2412 GLfloat scale = radius;
2415 glTranslatef(0, -6, 5); /* position on top of hand */
2417 glTranslatef(x, y, 0);
2418 glScalef(scale, scale, scale);
2419 glRotatef (s->angle / M_PI*180, 1, 0, 1);
2421 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor1);
2422 polys += unit_sphere (slices, slices, MI_IS_WIREFRAME(mi));
2424 glRotatef (90, 0, 0, 1);
2425 glTranslatef (0, 0, 0.81);
2426 glScalef(0.15, 0.15, 0.15);
2427 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, gcolor2);
2428 for (i = 0; i < 3; i++) {
2430 glTranslatef (0, 0, 1);
2431 glRotatef (360 * i / 3, 0, 0, 1);
2432 glTranslatef (2, 0, 0);
2433 glRotatef (18, 0, 1, 0);
2434 glBegin (MI_IS_WIREFRAME(mi) ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
2435 glVertex3f (0, 0, 0);
2436 for (j = slices; j >= 0; j--) {
2437 GLfloat th = j * M_PI*2 / slices;
2438 glVertex3f (cos(th), sin(th), 0);
2451 /**************************************************************************
2452 * Public Functions *
2454 **************************************************************************/
2458 release_juggle (ModeInfo * mi)
2460 if (juggles != NULL) {
2463 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2464 free_juggle(&juggles[screen]);
2466 juggles = (jugglestruct *) NULL;
2470 /* FIXME: refill_juggle currently just appends new throws to the
2471 * programme. This is fine if the programme is empty, but if there
2472 * are still some trajectories left then it really should take these
2476 refill_juggle(ModeInfo * mi)
2478 jugglestruct *sp = NULL;
2481 if (juggles == NULL)
2483 sp = &juggles[MI_SCREEN(mi)];
2485 /* generate pattern */
2487 if (pattern == NULL) {
2490 #define MAXREPEAT 300
2491 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2492 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2495 while (count < MI_CYCLES(mi)) {
2496 char buf[MAXPAT * 3 + 3], *b = buf;
2498 int l = NRAND(MAXPAT) + 1;
2499 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2501 { /* vary number of balls */
2502 int new_balls = sp->num_balls;
2505 if (new_balls == 2) /* Do not juggle 2 that often */
2506 change = NRAND(2 + CHANGE_BIAS / 4);
2508 change = NRAND(2 + CHANGE_BIAS);
2519 if (new_balls < sp->patternindex.minballs) {
2522 if (new_balls > sp->patternindex.maxballs) {
2525 if (new_balls < sp->num_balls) {
2526 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2529 sp->num_balls = new_balls;
2533 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2534 /* Pick from PortFolio */
2535 int p = sp->patternindex.index[sp->num_balls].start +
2536 NRAND(sp->patternindex.index[sp->num_balls].number);
2537 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2540 /* Invent a new pattern */
2542 for(i = 0; i < l; i++){
2544 do { /* Triangular Distribution => high values more likely */
2545 m = NRAND(sp->num_balls + 1);
2546 n = NRAND(sp->num_balls + 1);
2548 if (n == sp->num_balls) {
2551 switch(NRAND(5 + POSITION_BIAS)){
2552 case 0: /* Outside throw */
2554 case 1: /* Cross throw */
2556 case 2: /* Cross catch */
2558 case 3: /* Cross throw and catch */
2560 case 4: /* Bounce */
2563 break; /* Inside throw (default) */
2572 if (!program(mi, buf, NULL, t))
2577 } else { /* pattern supplied in height or 'a' notation */
2578 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2595 if (!projectile(sp)) {
2602 if(MI_IS_DEBUG(mi)) dump(sp);
2607 change_juggle(ModeInfo * mi)
2609 jugglestruct *sp = NULL;
2612 if (juggles == NULL)
2614 sp = &juggles[MI_SCREEN(mi)];
2616 /* Strip pending trajectories */
2617 for (t = sp->head->next; t != sp->head; t = t->next) {
2618 if(t->start > sp->time || t->finish < sp->time) {
2621 trajectory_destroy(n);
2625 /* Pick the current object theme */
2626 sp->objtypes = choose_object();
2630 mi->polygon_count += show_figure(mi, True);
2635 reshape_juggle (ModeInfo *mi, int width, int height)
2637 GLfloat h = (GLfloat) height / (GLfloat) width;
2639 glViewport (0, 0, (GLint) width, (GLint) height);
2641 glMatrixMode(GL_PROJECTION);
2643 gluPerspective (30.0, 1/h, 1.0, 100.0);
2645 glMatrixMode(GL_MODELVIEW);
2647 gluLookAt( 0.0, 0.0, 30.0,
2651 glClear(GL_COLOR_BUFFER_BIT);
2656 init_juggle (ModeInfo * mi)
2658 jugglestruct *sp = 0;
2659 int wire = MI_IS_WIREFRAME(mi);
2662 juggles = (jugglestruct *)
2663 calloc (MI_NUM_SCREENS(mi), sizeof (jugglestruct));
2665 fprintf(stderr, "%s: out of memory\n", progname);
2670 sp = &juggles[MI_SCREEN(mi)];
2672 sp->glx_context = init_GL(mi);
2674 # ifdef HAVE_GLBITMAP
2675 load_font (mi->dpy, "titleFont", &sp->mode_font, &sp->font_dlist);
2676 # else /* !HAVE_GLBITMAP */
2677 sp->font_data = load_texture_font (mi->dpy, "titleFont");
2678 # endif /* !HAVE_GLBITMAP */
2680 reshape_juggle (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
2681 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
2685 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
2686 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
2687 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
2688 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
2690 glEnable(GL_LIGHTING);
2691 glEnable(GL_LIGHT0);
2692 glEnable(GL_DEPTH_TEST);
2693 glEnable(GL_CULL_FACE);
2695 glLightfv(GL_LIGHT0, GL_POSITION, pos);
2696 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
2697 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
2698 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
2701 make_random_colormap (0, 0, 0,
2702 mi->colors, &MI_NPIXELS(mi),
2703 True, False, 0, False);
2706 double spin_speed = 0.05;
2707 double wander_speed = 0.001;
2708 double spin_accel = 0.05;
2709 sp->rot = make_rotator (0, spin_speed, 0,
2710 spin_accel, wander_speed, False);
2711 sp->trackball = gltrackball_init ();
2714 if (only && *only && strcmp(only, " ")) {
2715 balls = clubs = torches = knives = rings = bballs = False;
2716 if (!strcasecmp (only, "balls")) balls = True;
2717 else if (!strcasecmp (only, "clubs")) clubs = True;
2718 else if (!strcasecmp (only, "torches")) torches = True;
2719 else if (!strcasecmp (only, "knives")) knives = True;
2720 else if (!strcasecmp (only, "rings")) rings = True;
2721 else if (!strcasecmp (only, "bballs")) bballs = True;
2723 (void) fprintf (stderr,
2724 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2725 "\t rings, or bballs (not \"%s\")\n", only);
2726 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2732 /* #### hard to make this look good in OpenGL... */
2736 if (sp->head == 0) { /* first time initializing this juggler */
2738 sp->count = ABS(MI_COUNT(mi));
2742 /* record start time */
2743 sp->begintime = time(NULL);
2744 if(sp->patternindex.maxballs > 0) {
2745 sp->num_balls = sp->patternindex.minballs +
2746 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2749 mi->polygon_count +=
2750 show_figure(mi, True); /* Draw figure. Also discovers
2751 information about the juggler's
2754 /* "7" should be about three times the height of the juggler's
2756 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2757 7 * THROW_CATCH_INTERVAL);
2759 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2760 balls = True; /* Have to juggle something! */
2762 /* create circular trajectory list */
2763 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2764 if(sp->head == NULL){
2769 /* create circular object list */
2770 ADD_ELEMENT(Object, sp->objects, sp->objects);
2771 if(sp->objects == NULL){
2776 sp->pattern = strdup(""); /* Initialise saved pattern with
2780 sp = &juggles[MI_SCREEN(mi)];
2784 !strcasecmp (pattern, ".") ||
2785 !strcasecmp (pattern, "random")))
2788 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2789 /* pattern list needs indexing */
2790 int nelements = countof(portfolio);
2794 /* sort according to number of balls */
2795 qsort((void*)portfolio, nelements,
2796 sizeof(portfolio[1]), compare_num_balls);
2798 /* last pattern has most balls */
2799 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2800 /* run through sorted list, indexing start of each group
2801 and number in group */
2802 sp->patternindex.maxballs = 1;
2803 for (i = 0; i < nelements; i++) {
2804 int b = get_num_balls(portfolio[i].pattern);
2805 if (b > sp->patternindex.maxballs) {
2806 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2807 if(numpat == 0) sp->patternindex.minballs = b;
2808 sp->patternindex.maxballs = b;
2810 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2815 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2818 /* Set up programme */
2821 /* Only put things here that won't interrupt the programme during
2824 /* Use MIN so that users can resize in interesting ways, eg
2825 narrow windows for tall patterns, etc */
2826 sp->scale = MIN(SCENE_HEIGHT/480.0, SCENE_WIDTH/160.0);
2831 juggle_handle_event (ModeInfo *mi, XEvent *event)
2833 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2835 if (event->xany.type == ButtonPress &&
2836 event->xbutton.button == Button1)
2838 sp->button_down_p = True;
2839 gltrackball_start (sp->trackball,
2840 event->xbutton.x, event->xbutton.y,
2841 MI_WIDTH (mi), MI_HEIGHT (mi));
2844 else if (event->xany.type == ButtonRelease &&
2845 event->xbutton.button == Button1)
2847 sp->button_down_p = False;
2850 else if (event->xany.type == ButtonPress &&
2851 (event->xbutton.button == Button4 ||
2852 event->xbutton.button == Button5 ||
2853 event->xbutton.button == Button6 ||
2854 event->xbutton.button == Button7))
2856 gltrackball_mousewheel (sp->trackball, event->xbutton.button, 10,
2857 !!event->xbutton.state);
2860 else if (event->xany.type == MotionNotify &&
2863 gltrackball_track (sp->trackball,
2864 event->xmotion.x, event->xmotion.y,
2865 MI_WIDTH (mi), MI_HEIGHT (mi));
2868 else if (event->xany.type == KeyPress)
2872 XLookupString (&event->xkey, &c, 1, &keysym, 0);
2873 if (c == ' ' || c == '\n' || c == '\r' || c == '\t')
2886 draw_juggle (ModeInfo *mi)
2888 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2889 Display *dpy = MI_DISPLAY(mi);
2890 Window window = MI_WINDOW(mi);
2892 Trajectory *traj = NULL;
2894 unsigned long future = 0;
2895 char *pattern = NULL;
2897 if (!sp->glx_context)
2900 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sp->glx_context));
2902 glShadeModel(GL_SMOOTH);
2904 glEnable(GL_DEPTH_TEST);
2905 glEnable(GL_NORMALIZE);
2906 glEnable(GL_CULL_FACE);
2908 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
2911 glRotatef(current_device_rotation(), 0, 0, 1);
2913 glTranslatef(0,-3,0);
2917 get_position (sp->rot, &x, &y, &z, !sp->button_down_p);
2918 glTranslatef((x - 0.5) * 8,
2922 gltrackball_rotate (sp->trackball);
2924 get_rotation (sp->rot, &x, &y, &z, !sp->button_down_p);
2926 if (y < 0.8) y = 0.8 - (y - 0.8); /* always face forward */
2927 if (y > 1.2) y = 1.2 - (y - 1.2);
2929 glRotatef (x * 360, 1.0, 0.0, 0.0);
2930 glRotatef (y * 360, 0.0, 1.0, 0.0);
2931 glRotatef (z * 360, 0.0, 0.0, 1.0);
2935 GLfloat scale = 20.0 / SCENE_HEIGHT;
2936 glScalef(scale, scale, scale);
2939 glRotatef (180, 0, 0, 1);
2940 glTranslatef(-SCENE_WIDTH/2, -SCENE_HEIGHT/2, 0);
2941 glTranslatef(0, -150, 0);
2943 mi->polygon_count = 0;
2948 (void)gettimeofday(&tv, NULL);
2949 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2951 sp->time += MI_DELAY(mi) / 1000;
2954 /* First pass: Move arms and strip out expired elements */
2955 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2956 if (traj->status != PREDICTOR) {
2957 /* Skip any elements that need further processing */
2958 /* We could remove them, but there shoudn't be many and they
2959 would be needed if we ever got the pattern refiller
2963 if (traj->start > future) { /* Lookahead to the end of the show */
2964 future = traj->start;
2966 if (sp->time < traj->start) { /* early */
2968 } else if (sp->time < traj->finish) { /* working */
2970 /* Look for pattern name */
2971 if(traj->pattern != NULL) {
2972 pattern=traj->pattern;
2975 if (traj->type == Empty || traj->type == Full) {
2976 /* Only interested in hands on this pass */
2977 /* double angle = traj->angle + traj->spin * (sp->time - traj->start);*/
2978 double xd = 0, yd = 0;
2981 /* Find the catching offset */
2982 if(traj->object != NULL) {
2984 /* #### not sure what this is doing, but I'm guessing
2985 that the use of PERSPEC means this isn't needed
2986 in the OpenGL version? -jwz
2988 if(ObjectDefs[traj->object->type].handle > 0) {
2989 /* Handles Need to be oriented */
2990 xd = ObjectDefs[traj->object->type].handle *
2991 PERSPEC * sin(angle);
2992 yd = ObjectDefs[traj->object->type].handle *
2997 /* Balls are always caught at the bottom */
3002 p.x = (CUBIC(traj->xp, sp->time) - xd);
3003 p.y = (CUBIC(traj->yp, sp->time) + yd);
3004 reach_arm(mi, traj->hand, &p);
3006 /* Store updated hand position */
3010 if (traj->type == Ball || traj->type == Full) {
3011 /* Only interested in objects on this pass */
3015 if(traj->type == Full) {
3016 /* Adjusted these in the first pass */
3020 x = CUBIC(traj->xp, sp->time);
3021 y = CUBIC(traj->yp, sp->time);
3024 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
3027 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
3028 s->divisions = traj->divisions;
3029 traj->object->tracelen++;
3030 traj->object->active = True;
3032 } else { /* expired */
3033 Trajectory *n = traj;
3035 trajectory_destroy(n);
3040 mi->polygon_count += show_figure(mi, False);
3041 mi->polygon_count += show_arms(mi);
3044 glTranslatef(0, 0, ARMLENGTH);
3045 for (o = sp->objects->next; o != sp->objects; o = o->next) {
3047 mi->polygon_count += ObjectDefs[o->type].draw(mi, o->color,
3054 /* Save pattern name so we can erase it when it changes */
3055 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
3057 sp->pattern = strdup(pattern);
3059 if (MI_IS_VERBOSE(mi)) {
3060 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
3061 MI_SCREEN(mi), sp->pattern);
3065 print_gl_string (mi->dpy,
3066 # ifdef HAVE_GLBITMAP
3067 sp->mode_font, sp->font_dlist,
3068 # else /* !HAVE_GLBITMAP */
3070 # endif /* !HAVE_GLBITMAP */
3071 mi->xgwa.width, mi->xgwa.height,
3072 10, mi->xgwa.height - 10,
3073 sp->pattern, False);
3076 if((int)(sp->time/10) % 1000 == 0)
3077 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
3080 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
3082 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
3089 if (mi->fps_p) do_fps (mi);
3092 glXSwapBuffers(dpy, window);
3095 XSCREENSAVER_MODULE_2 ("Juggler3D", juggler3d, juggle)