1 /* -*- Mode: C; tab-width: 4 -*- */
4 #if !defined( lint ) && !defined( SABER )
5 static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore";
10 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
12 * Permission to use, copy, modify, and distribute this software and its
13 * documentation for any purpose and without fee is hereby granted,
14 * provided that the above copyright notice appear in all copies and that
15 * both that copyright notice and this permission notice appear in
16 * supporting documentation.
18 * This file is provided AS IS with no warranties of any kind. The author
19 * shall have no liability with respect to the infringement of copyrights,
20 * trade secrets or any patents by this file or any part thereof. In no
21 * event will the author be liable for any lost revenue or profits or
22 * other special, indirect and consequential damages.
25 * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
26 * Add -rings, -bballs. Add -describe. Finally made
27 * live pattern updates possible. Add refill_juggle(),
28 * change_juggle() and reshape_juggle(). Make
29 * init_juggle() non-destructive. Reorder erase/draw
30 * operations. Update xscreensaver xml and manpage.
31 * 15-Nov-2004: [TDA] Fix all memory leaks.
32 * 12-Nov-2004: [TDA] Add -torches and another new trail
33 * implementation, so that different objects can have
34 * different length trails.
35 * 11-Nov-2004: [TDA] Clap when all the balls are in the air.
36 * 10-Nov-2004: [TDA] Display pattern name converted to hight
38 * 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
39 * 02-Sep-2003: Non-real time to see what is happening without a
40 * strobe effect for slow machines.
41 * 01-Nov-2000: Allocation checks
47 * Implement the anonymously promised -uni option.
52 * Notes on Adam Chalcraft Juggling Notation (used by permission)
53 * a-> Adam's notation s-> Site swap (Cambridge) notation
55 * To define a map from a-notation to s-notation ("site-swap"), both
56 * of which look like doubly infinite sequences of natural numbers. In
57 * s-notation, there is a restriction on what is allowed, namely for
58 * the sequence s_n, the associated function f(n)=n+s_n must be a
59 * bijection. In a-notation, there is no restriction.
61 * To go from a-notation to s-notation, you start by mapping each a_n
62 * to a permutation of N, the natural numbers.
65 * 1 -> (10) [i.e. f(1)=0, f(0)=1]
66 * 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
67 * 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
70 * Then for each n, you look at how long 0 takes to get back to 0
71 * again and you call this t_n. If a_n=0, for example, then since the
72 * identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
73 * a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
74 * next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
75 * following the 1. Finally, set s_n = t_n - 1.
77 * To give some examples, it helps to have a notation for cyclic
78 * sequences. By (123), for example, I mean ...123123123123... . Now
79 * under the a-notation -> s-notation mapping we have some familiar
82 * (0)->(0), (1)->(1), (2)->(2) etc.
83 * (21)->(31), (31)->(51), (41)->(71) etc.
84 * (10)->(20), (20)->(40), (30)->(60) etc.
85 * (331)->(441), (312)->(612), (303)->(504), (321)->(531)
86 * (43)->(53), (434)->(534), (433)->(633)
89 * In general, the number of balls is the *average* of the s-notation,
90 * and the *maximum* of the a-notation. Another theorem is that the
91 * minimum values in the a-notation and the s-notation and equal, and
92 * preserved in the same positions.
94 * The usefulness of a-notation is the fact that there are no
95 * restrictions on what is allowed. This makes random juggle
96 * generation much easier. It also makes enumeration very
97 * easy. Another handy feature is computing changes. Suppose you can
98 * do (5) and want a neat change up to (771) in s-notation [Mike Day
99 * actually needed this example!]. Write them both in a-notation,
100 * which gives (5) and (551). Now concatenate them (in general, there
101 * may be more than one way to do this, but not in this example), to
104 * ...55555555551551551551551...
106 * Now convert back to s-notation, to get
108 * ...55555566771771771771771...
110 * So the answer is to do two 6 throws and then go straight into
111 * (771). Coming back down of course,
113 * ...5515515515515515555555555...
117 * ...7717717717716615555555555...
119 * so the answer is to do a single 661 and then drop straight down to
122 * [The number of balls in the generated pattern occasionally changes.
123 * In order to decrease the number of balls I had to introduce a new
124 * symbol into the Adam notation, [*] which means 'lose the current
128 /* This code uses so many linked lists it's worth having a built-in
134 #define PROGCLASS "Juggle"
135 #define HACK_INIT init_juggle
136 #define HACK_DRAW draw_juggle
137 #define HACK_RESHAPE reshape_juggle
138 #define _no_HACK_FREE release_juggle
139 #define juggle_opts xlockmore_opts
140 #define DEFAULTS "*delay: 10000 \n" \
144 "*font: -*-times-bold-r-normal-*-180-*\n"
146 #include "xlockmore.h" /* in xscreensaver distribution */
147 #define MI_DELAY(MI) ((MI)->pause)
149 # define MI_DEPTH MI_WIN_DEPTH
151 #else /* STANDALONE */
152 #include "xlock.h" /* in xlockmore distribution */
153 #endif /* STANDALONE */
158 #define XClearWindow(d, w) \
160 XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \
161 XFillRectangle(d, w, MI_GC(mi), \
162 0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \
166 #define DEF_PATTERN "." /* All patterns */
167 #define DEF_TAIL "1" /* No trace */
169 /* Maybe a ROLA BOLA would be at a better angle for viewing */
170 #define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
172 #define DEF_REAL "True"
173 #define DEF_DESCRIBE "True"
175 #define DEF_BALLS "True" /* Use Balls */
176 #define DEF_CLUBS "True" /* Use Clubs */
177 #define DEF_TORCHES "True" /* Use Torches */
178 #define DEF_KNIVES "True" /* Use Knives */
179 #define DEF_RINGS "True" /* Use Rings */
180 #define DEF_BBALLS "True" /* Use Bowling Balls */
183 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
186 static char *pattern;
192 static Bool describe;
201 static XrmOptionDescRec opts[] =
203 {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
204 {"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
206 {"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
207 {"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
209 {"-real", ".juggle.real", XrmoptionNoArg, "on" },
210 {"+real", ".juggle.real", XrmoptionNoArg, "off" },
211 {"-describe", ".juggle.describe", XrmoptionNoArg, "on" },
212 {"+describe", ".juggle.describe", XrmoptionNoArg, "off" },
213 {"-balls", ".juggle.balls", XrmoptionNoArg, "on" },
214 {"+balls", ".juggle.balls", XrmoptionNoArg, "off" },
215 {"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" },
216 {"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" },
217 {"-torches", ".juggle.torches", XrmoptionNoArg, "on" },
218 {"+torches", ".juggle.torches", XrmoptionNoArg, "off" },
219 {"-knives", ".juggle.knives", XrmoptionNoArg, "on" },
220 {"+knives", ".juggle.knives", XrmoptionNoArg, "off" },
221 {"-rings", ".juggle.rings", XrmoptionNoArg, "on" },
222 {"+rings", ".juggle.rings", XrmoptionNoArg, "off" },
223 {"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" },
224 {"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" },
225 {"-only", ".juggle.only", XrmoptionSepArg, NULL },
227 static argtype vars[] =
229 { &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
230 { &tail, "tail", "Tail", DEF_TAIL, t_Int },
232 { &uni, "uni", "Uni", DEF_UNI, t_Bool },
234 { &real, "real", "Real", DEF_REAL, t_Bool },
235 { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool },
236 { &balls, "balls", "Clubs", DEF_BALLS, t_Bool },
237 { &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool },
238 { &torches, "torches", "Torches", DEF_TORCHES, t_Bool },
239 { &knives, "knives", "Knives", DEF_KNIVES, t_Bool },
240 { &rings, "rings", "Rings", DEF_RINGS, t_Bool },
241 { &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool },
242 { &only, "only", "BBalls", " ", t_String },
244 static OptionStruct desc[] =
246 { "-pattern string", "Cambridge Juggling Pattern" },
247 { "-tail num", "Trace Juggling Patterns" },
249 { "-/+uni", "Unicycle" },
251 { "-/+real", "Real-time" },
252 { "-/+describe", "turn on/off pattern descriptions." },
253 { "-/+balls", "turn on/off Balls." },
254 { "-/+clubs", "turn on/off Clubs." },
255 { "-/+torches", "turn on/off Flaming Torches." },
256 { "-/+knives", "turn on/off Knives." },
257 { "-/+rings", "turn on/off Rings." },
258 { "-/+bballs", "turn on/off Bowling Balls." },
259 { "-only", "Turn off all objects but the named one." },
262 ModeSpecOpt juggle_opts =
263 { XtNumber(opts), opts, XtNumber(vars), vars, desc };
266 ModStruct juggle_description = {
267 "juggle", "init_juggle", "draw_juggle", "release_juggle",
268 "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
269 10000, 200, 1000, 1, 64, 1.0, "",
270 "Shows a Juggler, juggling", 0, NULL
276 #include <X11/unix_time.h>
279 #include <sys/time.h>
281 #if HAVE_SYS_SELECT_H
282 #include <sys/select.h>
286 /* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All
287 "thicknesses" are scaled by sqrt(sp->scale) so that they are
288 proportionally thicker for smaller windows. Objects spinning out
289 of the plane (such as clubs) fake perspective by compressing their
290 horizontal coordinates by PERSPEC */
294 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
296 #define BALLRADIUS ARMWIDTH
301 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
303 /* Timing based on count. Units are milliseconds. Juggles per second
304 is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
306 #define THROW_CATCH_INTERVAL (sp->count)
307 #define THROW_NULL_INTERVAL (sp->count * 0.5)
308 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
310 /********************************************************************
311 * Trace Definitions *
313 * These record rendering data so that a drawn object can be erased *
314 * later. Each object has its own Trace list. *
316 ********************************************************************/
318 typedef struct {double x, y; } DXPoint;
319 typedef struct trace *TracePtr;
320 typedef struct trace {
331 /*******************************************************************
332 * Object Definitions *
334 * These describe the various types of Object that can be juggled *
336 *******************************************************************/
337 typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
339 static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
340 static DrawProc show_ring, show_bball;
342 typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
343 NUM_OBJECT_TYPES} ObjType;
344 #define OBJMIXPROB 20 /* inverse of the chances of using an odd
345 object in the pattern */
347 static const struct {
348 DrawProc *draw; /* Object Rendering function */
349 int handle; /* Length of object's handle */
350 int mintrail; /* Minimum trail length */
351 double cor; /* Coefficient of Restitution. perfect bounce = 1 */
352 double weight; /* Heavier objects don't get thrown as high */
365 0.55, /* Clubs don't bounce too well */
371 20, /* Torches need flames */
372 0, /* Torches don't bounce -- fire risk! */
379 0, /* Knives don't bounce */
398 /**************************
399 * Trajectory definitions *
400 **************************/
402 typedef enum {HEIGHT, ADAM} Notation;
403 typedef enum {Empty, Full, Ball} Throwable;
404 typedef enum {LEFT, RIGHT} Hand;
405 typedef enum {THROW, CATCH} Action;
406 typedef enum {HAND, ELBOW, SHOULDER} Joint;
407 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
408 PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
409 typedef struct {double a, b, c, d; } Spline;
410 typedef DXPoint Arm[3];
412 /* A Wander contains a Spline and a time interval. A list of Wanders
413 * describes the performer's position as he moves around the screen. */
414 typedef struct wander *WanderPtr;
415 typedef struct wander {
416 WanderPtr next, prev;
418 unsigned long finish;
425 /* Object is an arbitrary object being juggled. Each Trajectory
426 * references an Object ("count" tracks this), and each Object is also
427 * linked into a global Objects list. Objects may include a Trace
428 * list for tracking erasures. */
429 typedef struct object *ObjectPtr;
430 typedef struct object {
431 ObjectPtr next, prev;
435 int count; /* reference count */
436 Bool active; /* Object is in use */
446 /* Trajectory is a segment of juggling action. A list of Trajectories
447 * defines the juggling performance. The Trajectory list goes through
448 * multiple processing steps to convert it from basic juggling
449 * notation into rendering data. */
451 typedef struct trajectory *TrajectoryPtr;
452 typedef struct trajectory {
453 TrajectoryPtr prev, next; /* for building list */
454 TrajectoryStatus status;
472 TrajectoryPtr balllink;
473 TrajectoryPtr handlink;
476 double cx; /* Moving juggler */
477 double x, y; /* current position */
478 double dx, dy; /* initial velocity */
482 unsigned long start, finish;
491 /* Jugglestruct: per-screen global data. The master Wander, Object
492 * and Trajectory lists are anchored here. */
503 time_t begintime; /* should make 'time' usable for at least 48 days
504 on a 32-bit machine */
505 unsigned long time; /* millisecond timer*/
510 static jugglestruct *juggles = (jugglestruct *) NULL;
512 static XFontStruct *mode_font = None;
519 const char * pattern;
523 /* List of popular patterns, in any order */
524 /* Patterns should be given in Adam notation so the generator can
525 concatenate them safely. Null descriptions are ok. Height
526 notation will be displayed automatically. */
527 static patternstruct portfolio[] = {
528 {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
529 {"[2 0]", /* 4 0 */ "2 in 1 hand"},
530 {"[2 0 1]", /* 5 0 1 */},
531 {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
532 {"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
533 {"[2 0 1 1]", /* 6 0 1 1 */},
535 {"[3]", /* 3 */ "3 cascade"},
536 {"[+3]", /* +3 */ "reverse 3 cascade"},
537 {"[=3]", /* =3 */ "cascade 3 under arm"},
538 {"[&3]", /* &3 */ "cascade 3 catching under arm"},
539 {"[_3]", /* _3 */ "bouncing 3 cascade"},
540 {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
541 {"[3 2 1]", /* 5 3 1" */},
542 {"[3 3 1]", /* 4 4 1" */},
543 {"[3 1 2]", /* 6 1 2 */ "See-saw"},
544 {"[=3 3 1 2]", /* =4 5 1 2 */},
545 {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
546 {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
547 {"[3 3 1]", /* 4 4 1 */},
548 {"[+3 2 3]", /* +4 2 3 */},
549 {"[+3 1]", /* +5 1 */ "3 shower"},
550 {"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
551 {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
552 {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
553 {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
554 {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
555 {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
556 {"[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 */},
557 {"[3 0 2 0]", /* 8 0 4 0 */},
558 {"[_3 2 1]", /* _5 3 1 */},
559 {"[_3 0 1]", /* _8 0 1 */},
560 {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
561 {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
563 {"[4]", /* 4 */ "4 cascade"},
564 {"[+4 3]", /* +5 3 */ "4 ball half shower"},
565 {"[4 4 2]", /* 5 5 2 */},
566 {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
567 {"[+4 3 +4]", /* +5 3 +4 */},
568 {"[4 3 4 4]", /* 5 3 4 4 */},
569 {"[4 3 3 4]", /* 6 3 3 4 */},
570 {"[4 3 2 4", /* 6 4 2 4 */},
571 {"[+4 1]", /* +7 1 */ "4 shower"},
572 {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
573 {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
574 {"[+4 2 1 3]", /* +9 3 1 3 */},
575 {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
576 {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
577 {"[_4 3 3]", /* _6 3 3 */},
578 {"[_4 3 1]", /* _7 4 1 */},
579 {"[_4 2 1]", /* _8 3 1 */},
580 {"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
581 {"[_4 1 3 1]", /* _9 1 5 1 */},
582 {"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
584 {"[5]", /* 5 */ "5 cascade"},
585 {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
586 {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
587 {"[5 4 4]", /* 7 4 4 */},
588 {"[_5 4 4]", /* _7 4 4 */},
589 {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
590 {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
591 {"[_5 4 1 +4]", /* _9 5 1 5 */},
592 {"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
593 {"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
595 {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
597 {"[6]", /* 6 */ "6 cascade"},
598 {"[+6 5]", /* +7 5 */},
599 {"[6 4]", /* 8 4 */},
600 {"[+6 3]", /* +9 3 */},
601 {"[6 5 4 4]", /* 9 7 4 4 */},
602 {"[+6 5 5 5]", /* +9 5 5 5 */},
603 {"[6 0 6]", /* 9 0 9 */},
604 {"[_6 0 _6]", /* _9 0 _9 */},
606 {"[_7]", /* _7 */ "bouncing 7 cascade"},
607 {"[7]", /* 7 */ "7 cascade"},
608 {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
612 typedef struct { int start; int number; } PatternIndex;
617 PatternIndex index[XtNumber(portfolio)];
624 #define DUP_OBJECT(n, t) { \
625 (n)->object = (t)->object; \
626 if((n)->object != NULL) (n)->object->count++; \
629 /* t must point to an existing element. t must not be an
630 expression ending ->next or ->prev */
631 #define REMOVE(t) { \
632 (t)->next->prev = (t)->prev; \
633 (t)->prev->next = (t)->next; \
637 /* t receives element to be created and added to the list. ot must
638 point to an existing element or be identical to t to start a new
639 list. Applicable to Trajectories, Objects and Traces. */
640 #define ADD_ELEMENT(type, t, ot) \
641 if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
642 (t)->next = (ot)->next; \
645 (t)->next->prev = (t); \
649 object_destroy(Object* o)
651 if(o->trace != NULL) {
652 while(o->trace->next != o->trace) {
653 Trace *s = o->trace->next;
654 REMOVE(s); /* Don't eliminate 's' */
662 trajectory_destroy(Trajectory *t) {
663 if(t->name != NULL) free(t->name);
664 if(t->pattern != NULL) free(t->pattern);
665 /* Reduce object link count and call destructor if necessary */
666 if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
667 object_destroy(t->object);
669 REMOVE(t); /* Unlink and free */
673 free_juggle(jugglestruct *sp) {
674 if (sp->head != NULL) {
675 while (sp->head->next != sp->head) {
676 trajectory_destroy(sp->head->next);
679 sp->head = (Trajectory *) NULL;
681 if(sp->objects != NULL) {
682 while (sp->objects->next != sp->objects) {
683 object_destroy(sp->objects->next);
686 sp->objects = (Object*)NULL;
688 if(sp->wander != NULL) {
689 while (sp->wander->next != sp->wander) {
690 Wander *w = sp->wander->next;
694 sp->wander = (Wander*)NULL;
696 if(sp->pattern != NULL) {
703 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
707 ADD_ELEMENT(Trajectory, t, sp->head->prev);
708 if(t == NULL){ /* Out of Memory */
714 t->name = strdup(name);
727 /* add a Thratch to the performance */
729 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
731 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
737 if (MI_IS_VERBOSE(mi)) {
738 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
739 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
742 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
743 have been programmed */
744 /* title is the pattern name to be supplied to the first throw of
745 a sequence. If no name if given, use an empty title so that
746 the sequences are still delimited. */
747 const char *title = (name != NULL)? name : "";
752 for(p=patn; *p; p++) {
753 if (*p >= '0' && *p <='9') {
755 h = 10*h + (*p - '0');
757 Notation nn = notation;
759 case '[': /* begin Adam notation */
762 case '-': /* Inside throw */
765 case '+': /* Outside throw */
766 case '=': /* Cross throw */
767 case '&': /* Cross catch */
768 case 'x': /* Cross throw and catch */
769 case '_': /* Bounce */
770 case 'k': /* Kickup */
773 case '*': /* Lose ball */
777 case ']': /* end Adam notation */
783 if (!add_throw(sp, type, h, notation, title))
793 if(w == 0) { /* Only warn on first pass */
794 (void) fprintf(stderr,
795 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
802 if (seen) { /* end of sequence */
803 if (!add_throw(sp, type, h, notation, title))
817 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
819 4 4 1 3 12 2 4 1 4 4 13 0 3 1
822 #define BOUNCEOVER 10
826 /* Convert Adam notation into heights */
828 adam(jugglestruct *sp)
831 for(t = sp->head->next; t != sp->head; t = t->next) {
832 if (t->status == ATCH) {
835 for(p = t->next; a > 0; p = p->next) {
837 t->height = -9; /* Indicate end of processing for name() */
840 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
845 if(t->height > BOUNCEOVER && t->posn == ' '){
846 t->posn = '_'; /* high defaults can be bounced */
847 } else if(t->height < 3 && t->posn == '_') {
848 t->posn = ' '; /* Can't bounce short throws. */
850 if(t->height < KICKMIN && t->posn == 'k'){
851 t->posn = ' '; /* Can't kick short throws */
853 if(t->height > THROWMAX){
854 t->posn = 'k'; /* Use kicks for ridiculously high throws */
861 /* Discover converted heights and update the sequence title */
863 name(jugglestruct *sp)
868 for(t = sp->head->next; t != sp->head; t = t->next) {
869 if (t->status == THRATCH && t->name != NULL) {
871 for(p = t; p == t || p->name == NULL; p = p->next) {
872 if(p == sp->head || p->height < 0) { /* end of reliable data */
876 b += sprintf(b, " %d", p->height);
878 b += sprintf(b, " %c%d", p->posn, p->height);
880 if(b - buffer > 500) break; /* otherwise this could eventually
881 overflow. It'll be too big to
885 (void) sprintf(b, ", %s", t->name);
887 free(t->name); /* Don't need name any more, it's been converted
890 if(t->pattern != NULL) free(t->pattern);
891 t->pattern = strdup(buffer);
896 /* Split Thratch notation into explicit throws and catches.
897 Usually Catch follows Throw in same hand, but take care of special
900 /* ..n1.. -> .. LTn RT1 LC RC .. */
901 /* ..nm.. -> .. LTn LC RTm RC .. */
904 part(jugglestruct *sp)
906 Trajectory *t, *nt, *p;
907 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
909 for (t = sp->head->next; t != sp->head; t = t->next) {
910 if (t->status > THRATCH) {
912 } else if (t->status == THRATCH) {
915 /* plausibility check */
916 if (t->height <= 2 && t->posn == '_') {
917 t->posn = ' '; /* no short bounces */
919 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
920 t->posn = ' '; /* 1's need close catches */
925 case ' ': posn = '-'; t->posn = '+'; break;
926 case '+': posn = '+'; t->posn = '-'; break;
927 case '=': posn = '='; t->posn = '+'; break;
928 case '&': posn = '+'; t->posn = '='; break;
929 case 'x': posn = '='; t->posn = '='; break;
930 case '_': posn = '_'; t->posn = '-'; break;
931 case 'k': posn = 'k'; t->posn = 'k'; break;
933 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
936 hand = (Hand) ((hand + 1) % 2);
941 if (t->height == 1 && p != sp->head) {
942 p = p->prev; /* '1's are thrown earlier than usual */
948 ADD_ELEMENT(Trajectory, nt, p);
956 nt->height = t->height;
966 choose_object(void) {
969 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
970 if(balls && o == BALL) break;
971 if(clubs && o == CLUB) break;
972 if(torches && o == TORCH) break;
973 if(knives && o == KNIFE) break;
974 if(rings && o == RING) break;
975 if(bballs && o == BBALLS) break;
980 /* Connnect up throws and catches to figure out which ball goes where.
981 Do the same with the juggler's hands. */
986 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
989 for (t = sp->head->next; t != sp->head; t = t->next) {
990 if (t->status == ACTION) {
991 if (t->action == THROW) {
992 if (t->type == Empty) {
993 /* Create new Object */
994 ADD_ELEMENT(Object, t->object, sp->objects);
995 t->object->count = 1;
996 t->object->tracelen = 0;
997 t->object->active = False;
998 /* Initialise object's circular trace list */
999 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
1001 if (MI_NPIXELS(mi) > 2) {
1002 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
1005 t->object->color = 1;
1007 t->object->color = 0;
1011 /* Small chance of picking a random object instead of the
1013 if(NRAND(OBJMIXPROB) == 0) {
1014 t->object->type = choose_object();
1016 t->object->type = sp->objtypes;
1019 /* Check to see if we need trails for this object */
1020 if(tail < ObjectDefs[t->object->type].mintrail) {
1021 t->object->tail = ObjectDefs[t->object->type].mintrail;
1023 t->object->tail = tail;
1027 /* Balls can change divisions at each throw */
1028 t->divisions = 2 * (NRAND(2) + 1);
1030 /* search forward for next catch in this hand */
1031 for (p = t->next; t->handlink == NULL; p = p->next) {
1032 if(p->status < ACTION || p == sp->head) return;
1033 if (p->action == CATCH) {
1034 if (t->handlink == NULL && p->hand == t->hand) {
1040 if (t->height > 0) {
1043 /* search forward for next ball catch */
1044 for (p = t->next; t->balllink == NULL; p = p->next) {
1045 if(p->status < ACTION || p == sp->head) {
1049 if (p->action == CATCH) {
1050 if (t->balllink == NULL && --h < 1) { /* caught */
1051 t->balllink = p; /* complete trajectory */
1053 if (p->type == Full) {
1054 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1055 MI_SCREEN(mi), t->object->color);
1059 DUP_OBJECT(p, t); /* accept catch */
1060 p->angle = t->angle;
1061 p->divisions = t->divisions;
1066 t->type = Empty; /* thrown */
1067 } else if (t->action == CATCH) {
1068 /* search forward for next throw from this hand */
1069 for (p = t->next; t->handlink == NULL; p = p->next) {
1070 if(p->status < ACTION || p == sp->head) return;
1071 if (p->action == THROW && p->hand == t->hand) {
1072 p->type = t->type; /* pass ball */
1073 DUP_OBJECT(p, t); /* pass object */
1074 p->divisions = t->divisions;
1079 t->status = LINKEDACTION;
1084 /* Clap when both hands are empty */
1086 clap(jugglestruct *sp)
1089 for (t = sp->head->next; t != sp->head; t = t->next) {
1090 if (t->status == LINKEDACTION &&
1091 t->action == CATCH &&
1093 t->handlink != NULL &&
1094 t->handlink->height == 0) { /* Completely idle hand */
1096 for (p = t->next; p != sp->head; p = p->next) {
1097 if (p->status == LINKEDACTION &&
1098 p->action == CATCH &&
1099 p->hand != t->hand) { /* Next catch other hand */
1100 if(p->type == Empty &&
1101 p->handlink != NULL &&
1102 p->handlink->height == 0) { /* Also completely idle */
1104 t->handlink->posn = '^'; /* Move first hand's empty throw */
1105 p->posn = '^'; /* to meet second hand's empty
1109 break; /* Only need first catch */
1116 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1118 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1119 with velocity dx1 at time t1 */
1121 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1130 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1131 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1136 s.c = (3*a*t0 - 2*b)*t0 + c;
1137 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1141 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1142 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1143 velocity dx2 at time t2. The arrival and departure velocities at
1144 x1, t1 must be the same. */
1146 makeSplinePair(Spline *s1, Spline *s2,
1147 double x0, double dx0, int t0,
1149 double x2, double dx2, int t2)
1151 double x10, x21, t21, t10, t20, dx1;
1157 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1158 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1160 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1161 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1165 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1166 from x at time t at constant velocity dx. sy goes from y at time t
1167 with velocity dy and constant acceleration g. */
1169 makeParabola(Trajectory *n,
1170 double x, double dx, double y, double dy, double g)
1172 double t = (double)n->start;
1176 n->xp.d = -dx*t + x;
1179 n->yp.c = -g*t + dy;
1180 n->yp.d = g/2*t*t - dy*t + y;
1185 /* Make juggler wander around the screen */
1186 static double wander(jugglestruct *sp, unsigned long time)
1189 for (w = sp->wander->next; w != sp->wander; w = w->next) {
1190 if (w->finish < sp->time) { /* expired */
1194 } else if(w->finish > time) {
1198 if(w == sp->wander) { /* Need a new one */
1199 ADD_ELEMENT(Wander, w, sp->wander->prev);
1200 if(w == NULL) { /* Memory problem */
1203 w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1207 w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1209 w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1211 return CUBIC(w->s, time);
1214 #define SX 25 /* Shoulder Width */
1216 /* Convert hand position symbols into actual time/space coordinates */
1218 positions(jugglestruct *sp)
1221 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1222 for (t = sp->head->next; t != sp->head; t = t->next) {
1223 if (t->status >= PTHRATCH) {
1225 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1226 /* Allow ACTIONs to be annotated, but we won't mark them ready
1227 for the next stage */
1234 if (t->action == CATCH) { /* Throw-to-catch */
1235 if (t->type == Empty) {
1236 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1237 } else { /* successful catch */
1238 now += (int)(THROW_CATCH_INTERVAL);
1240 } else { /* Catch-to-throw */
1241 if(t->object != NULL) {
1242 now += (int) (CATCH_THROW_INTERVAL *
1243 ObjectDefs[t->object->type].weight);
1245 now += (int) (CATCH_THROW_INTERVAL);
1251 else /* Concatenated performances may need clock resync */
1254 t->cx = wander(sp, t->start);
1259 /* Add room for the handle */
1260 if(t->action == CATCH && t->object != NULL)
1261 yo -= ObjectDefs[t->object->type].handle;
1264 case '-': xo = sx - pose; break;
1267 case '+': xo = sx + pose; break;
1269 case '=': xo = - sx - pose; yo += pose; break;
1270 case '^': xo = 0; yo += pose*2; break; /* clap */
1272 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1276 t->angle = (((t->hand == LEFT) ^
1277 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1280 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1283 /* Only mark complete if it was already linked */
1284 if(t->status == LINKEDACTION) {
1285 t->status = PTHRATCH;
1292 /* Private physics functions */
1294 /* Compute the spin-rate for a trajectory. Different types of throw
1295 (eg, regular thows, bounces, kicks, etc) have different spin
1298 type = type of object
1299 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1300 old = earlier spin to consider
1301 dt = time span of this trajectory
1302 height = height of ball throw or 0 if based on old spin
1303 turns = full club turns required during this operation
1304 togo = partial club turns required to match hands
1307 spinrate(ObjType type, Trajectory *h, double old, double dt,
1308 int height, int turns, double togo)
1310 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1312 if(ObjectDefs[type].handle != 0) { /* Clubs */
1313 return (dir * turns * 2 * M_PI + togo) / dt;
1314 } else if(height == 0) { /* Balls already spinning */
1316 } else { /* Balls */
1317 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1322 /* compute the angle at the end of a spinning trajectory */
1324 end_spin(Trajectory *t)
1326 return t->angle + t->spin * (t->finish - t->start);
1329 /* Sets the initial angle of the catch following hand movement t to
1330 the final angle of the throw n. Also sets the angle of the
1331 subsequent throw to the same angle plus half a turn. */
1333 match_spins_on_catch(Trajectory *t, Trajectory *n)
1335 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1336 t->balllink->angle = end_spin(n);
1337 if(t->balllink->handlink != NULL) {
1338 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1344 find_bounce(jugglestruct *sp,
1345 double yo, double yf, double yc, double tc, double cor)
1347 double tb, i, dy = 0;
1348 const double e = 1; /* permissible error in yc */
1352 yt = height at catch time after one bounce
1353 one or three roots according to timing
1354 find one by interval bisection
1357 for(i = tc / 2; i > 0.0001; i/=2){
1360 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1363 dy = (yf - yo)/tb + sp->Gr/2*tb;
1365 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1368 }else if(yt > yc - e){
1374 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1381 new_predictor(const Trajectory *t, int start, int finish, double angle)
1384 ADD_ELEMENT(Trajectory, n, t->prev);
1389 n->divisions = t->divisions;
1391 n->status = PREDICTOR;
1399 /* Turn abstract timings into physically appropriate object trajectories. */
1401 projectile(jugglestruct *sp)
1404 const int yf = 0; /* Floor height */
1406 for (t = sp->head->next; t != sp->head; t = t->next) {
1407 if (t->status != PTHRATCH || t->action != THROW) {
1409 } else if (t->balllink == NULL) { /* Zero Throw */
1410 t->status = BPREDICTOR;
1411 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1413 } else if(t->balllink == t->handlink) {
1414 /* '2' height - hold on to ball. Don't need to consider
1415 flourishes, 'hands' will do that automatically anyway */
1418 /* Zero spin to avoid wrist injuries */
1420 match_spins_on_catch(t, t);
1422 t->status = BPREDICTOR;
1425 if (t->posn == '_') { /* Bounce once */
1427 const int tb = t->start +
1428 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1429 (double) (t->balllink->start - t->start),
1430 ObjectDefs[t->object->type].cor);
1432 if(tb < t->start) { /* bounce too hard */
1433 t->posn = '+'; /* Use regular throw */
1435 Trajectory *n; /* First (throw) trajectory. */
1436 double dt; /* Time span of a trajectory */
1437 double dy; /* Distance span of a follow-on trajectory.
1438 First trajectory uses t->dy */
1439 /* dx is constant across both trajectories */
1440 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1442 { /* ball follows parabola down */
1443 n = new_predictor(t, t->start, tb, t->angle);
1444 if(n == NULL) return False;
1445 dt = n->finish - n->start;
1446 /* Ball rate 4, no flight or matching club turns */
1447 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1448 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1449 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1452 { /* ball follows parabola up */
1453 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1455 if(m == NULL) return False;
1456 dt = m->finish - m->start;
1457 /* Use previous ball rate, no flight club turns */
1458 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1459 t->balllink->angle - m->angle);
1460 match_spins_on_catch(t, m);
1461 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1462 makeParabola(m, t->balllink->x - t->dx * dt,
1463 t->dx, (double) yf, dy, sp->Gr);
1466 t->status = BPREDICTOR;
1469 } else if (t->posn == 'k') { /* Drop & Kick */
1470 Trajectory *n; /* First (drop) trajectory. */
1471 Trajectory *o; /* Second (rest) trajectory */
1472 Trajectory *m; /* Third (kick) trajectory */
1473 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1474 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1477 { /* Fall to ground */
1478 n = new_predictor(t, t->start, td, t->angle);
1479 if(n == NULL) return False;
1480 dt = n->finish - n->start;
1481 /* Ball spin rate 4, no flight club turns */
1482 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1483 t->balllink->angle - n->angle);
1484 t->dx = (t->balllink->x - t->x) / dt;
1485 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1486 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1489 { /* Rest on ground */
1490 o = new_predictor(t, n->finish, tk, end_spin(n));
1491 if(o == NULL) return False;
1493 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1498 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1499 if(m == NULL) return False;
1500 dt = m->finish - m->start;
1501 /* Match receiving hand, ball rate 4, one flight club turn */
1502 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1503 4, 1, t->balllink->angle - m->angle);
1504 match_spins_on_catch(t, m);
1505 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1506 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1509 t->status = BPREDICTOR;
1513 /* Regular flight, no bounce */
1514 { /* ball follows parabola */
1516 Trajectory *n = new_predictor(t, t->start,
1517 t->balllink->start, t->angle);
1518 if(n == NULL) return False;
1519 dt = t->balllink->start - t->start;
1521 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1522 t->balllink->angle - n->angle);
1523 match_spins_on_catch(t, n);
1524 t->dx = (t->balllink->x - t->x) / dt;
1525 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1526 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1529 t->status = BPREDICTOR;
1535 /* Turn abstract hand motions into cubic splines. */
1537 hands(jugglestruct *sp)
1539 Trajectory *t, *u, *v;
1541 for (t = sp->head->next; t != sp->head; t = t->next) {
1542 /* no throw => no velocity */
1543 if (t->status != BPREDICTOR) {
1548 if (u == NULL) { /* no next catch */
1552 if (v == NULL) { /* no next throw */
1556 /* double spline takes hand from throw, thru catch, to
1559 t->finish = u->start;
1560 t->status = PREDICTOR;
1562 u->finish = v->start;
1563 u->status = PREDICTOR;
1566 /* FIXME: These adjustments leave a small glitch when alternating
1567 balls and clubs. Just hope no-one notices. :-) */
1569 /* make sure empty hand spin matches the thrown object in case it
1572 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1573 fabs((u->angle - t->angle)/(u->start - t->start));
1575 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1576 fabs((v->angle - u->angle)/(v->start - u->start));
1578 (void) makeSplinePair(&t->xp, &u->xp,
1579 t->x, t->dx, t->start,
1581 v->x, v->dx, v->start);
1582 (void) makeSplinePair(&t->yp, &u->yp,
1583 t->y, t->dy, t->start,
1585 v->y, v->dy, v->start);
1587 t->status = PREDICTOR;
1591 /* Given target x, y find_elbow puts hand at target if possible,
1592 * otherwise makes hand point to the target */
1594 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1598 double x = p->x - s->x;
1599 double y = p->y - s->y;
1600 h2 = x*x + y*y + z*z;
1601 if (h2 > 4 * armlength * armlength) {
1602 t = armlength/sqrt(h2);
1605 h->x = 2 * t * x + s->x;
1606 h->y = 2 * t * y + s->y;
1608 r = sqrt((double)(x*x + z*z));
1609 t = sqrt(4 * armlength * armlength / h2 - 1);
1610 e->x = x*(1 + y*t/r)/2 + s->x;
1611 e->y = (y - r*t)/2 + s->y;
1618 /* NOTE: returned x, y adjusted for arm reach */
1620 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1622 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1624 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1625 *p = sp->arm[1][side][HAND] = h;
1626 sp->arm[1][side][ELBOW] = e;
1630 /* dumps a human-readable rendition of the current state of the juggle
1631 pipeline to stderr for debugging */
1633 dump(jugglestruct *sp)
1636 for (t = sp->head->next; t != sp->head; t = t->next) {
1637 switch (t->status) {
1639 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1642 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1643 t->pattern == NULL?"":t->pattern);
1646 if (t->action == CATCH)
1647 (void) fprintf(stderr, "%p A %c%cC\n",
1649 t->hand ? 'R' : 'L');
1651 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1653 t->hand ? 'R' : 'L',
1654 (t->action == THROW)?'T':'N',
1658 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1661 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1662 t->height, t->object == NULL?0:t->object->color,
1663 (void*)t->handlink, (void*)t->balllink);
1666 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1669 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1670 t->height, t->type, t->object == NULL?0:t->object->color,
1671 t->start, t->finish);
1674 (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1675 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1676 t->object == NULL?0:t->object->color,
1677 t->start, t->finish, t->yp.c);
1680 (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1681 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1682 t->object == NULL?0:t->object->color,
1683 t->start, t->finish, t->yp.c);
1686 (void) fprintf(stderr, "%p: status %d not implemented\n",
1687 (void*)t, t->status);
1691 (void) fprintf(stderr, "---\n");
1695 static int get_num_balls(const char *j)
1700 for (p = j; *p; p++) {
1701 if (*p >= '0' && *p <='9') { /* digit */
1702 h = 10*h + (*p - '0');
1718 compare_num_balls(const void *p1, const void *p2)
1721 i = get_num_balls(((patternstruct*)p1)->pattern);
1722 j = get_num_balls(((patternstruct*)p2)->pattern);
1737 /**************************************************************************
1738 * Rendering Functions *
1740 **************************************************************************/
1743 show_arms(ModeInfo * mi, unsigned long color)
1745 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1748 XPoint a[XtNumber(sp->arm[0][0])];
1749 if(color == MI_BLACK_PIXEL(mi)) {
1754 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1755 ARMWIDTH, LineSolid, CapRound, JoinRound);
1756 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1757 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1758 /* Translate into device coords */
1759 for(i = 0; i < XtNumber(a); i++) {
1760 a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1761 a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
1763 sp->arm[0][side][i] = sp->arm[1][side][i];
1765 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1766 a, XtNumber(a), CoordModeOrigin);
1771 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1773 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1794 static const XPoint figure[] = {
1795 { 15, 70}, /* 0 Left Hip */
1796 { 0, 90}, /* 1 Waist */
1797 { SX, 130}, /* 2 Left Shoulder */
1798 {-SX, 130}, /* 3 Right Shoulder */
1799 {-15, 70}, /* 4 Right Hip */
1800 { 0, 130}, /* 5 Neck */
1801 { 0, 140}, /* 6 Chin */
1802 { SX, 0}, /* 7 Left Foot */
1803 {-SX, 0}, /* 8 Right Foot */
1804 {-17, 174}, /* 9 Head1 */
1805 { 17, 140}, /* 10 Head2 */
1807 XPoint a[XtNumber(figure)];
1809 /* Translate into device coords */
1810 for(i = 0; i < XtNumber(figure); i++) {
1811 a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1812 a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1815 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1816 ARMWIDTH, LineSolid, CapRound, JoinRound);
1817 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1820 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1821 p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1822 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1823 p, i, CoordModeOrigin);
1826 p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1827 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1828 p, i, CoordModeOrigin);
1831 p[i++] = a[5]; p[i++] = a[6];
1832 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1833 p, i, CoordModeOrigin);
1836 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1838 a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1839 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1840 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1842 /* Initialise arms */
1844 for(i = 0; i < 2; i++){
1845 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1846 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1847 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1848 sp->arm[i][LEFT][HAND].x = figure[0].x;
1849 sp->arm[i][LEFT][HAND].y = figure[1].y;
1850 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1851 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1852 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1853 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1854 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1860 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1862 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1863 int offset = (int)(s->angle*64*180/M_PI);
1864 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1865 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1867 /* Avoid wrapping */
1868 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1870 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1871 if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) {
1872 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1873 x - BALLRADIUS, y - BALLRADIUS,
1874 2*BALLRADIUS, 2*BALLRADIUS,
1876 } else if (s->divisions == 4) { /* 90 degree divisions */
1877 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1878 x - BALLRADIUS, y - BALLRADIUS,
1879 2*BALLRADIUS, 2*BALLRADIUS,
1880 offset % 23040, 5760);
1881 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1882 x - BALLRADIUS, y - BALLRADIUS,
1883 2*BALLRADIUS, 2*BALLRADIUS,
1884 (offset + 11520) % 23040, 5760);
1886 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1887 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1888 x - BALLRADIUS, y - BALLRADIUS,
1889 2*BALLRADIUS, 2*BALLRADIUS,
1890 (offset + 5760) % 23040, 5760);
1891 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1892 x - BALLRADIUS, y - BALLRADIUS,
1893 2*BALLRADIUS, 2*BALLRADIUS,
1894 (offset + 17280) % 23040, 5760);
1895 } else if (s->divisions == 2) { /* 180 degree divisions */
1896 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1897 x - BALLRADIUS, y - BALLRADIUS,
1898 2*BALLRADIUS, 2*BALLRADIUS,
1899 offset % 23040, 11520);
1901 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1902 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1903 x - BALLRADIUS, y - BALLRADIUS,
1904 2*BALLRADIUS, 2*BALLRADIUS,
1905 (offset + 11520) % 23040, 11520);
1907 (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1908 MI_SCREEN(mi), s->divisions);
1913 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1915 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1917 const double sa = sin(s->angle);
1918 const double ca = cos(s->angle);
1939 static const XPoint club[] = {
1952 {-24, 2}, /* 0 close boundary */
1954 XPoint a[XtNumber(club)];
1956 /* Avoid wrapping */
1957 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1959 /* Translate and fake perspective */
1960 for(i = 0; i < XtNumber(club); i++) {
1961 a[i].x = (short)(MI_WIDTH(mi)/2 +
1962 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1963 club[i].y*sqrt(sp->scale)*ca);
1964 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1965 club[i].y*sa*sqrt(sp->scale));
1968 if(color != MI_BLACK_PIXEL(mi)) {
1969 /* Outline in black */
1970 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1971 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1972 LineSolid, CapRound, JoinRound);
1973 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1974 a, XtNumber(a), CoordModeOrigin);
1977 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1979 /* Don't be tempted to optimize erase by drawing all the black in
1980 one X operation. It must use the same ops as the colours to
1981 guarantee a clean erase. */
1983 i = 0; /* Colored stripes */
1984 p[i++] = a[1]; p[i++] = a[2];
1985 p[i++] = a[9]; p[i++] = a[10];
1986 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1987 p, i, Convex, CoordModeOrigin);
1989 p[i++] = a[3]; p[i++] = a[4];
1990 p[i++] = a[7]; p[i++] = a[8];
1991 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1992 p, i, Convex, CoordModeOrigin);
1994 if(color != MI_BLACK_PIXEL(mi)) {
1995 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1998 i = 0; /* White center band */
1999 p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
2000 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2001 p, i, Convex, CoordModeOrigin);
2003 i = 0; /* White handle */
2004 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2005 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2006 p, i, Convex, CoordModeOrigin);
2008 i = 0; /* White tip */
2009 p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2010 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2011 p, i, Convex, CoordModeOrigin);
2016 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2018 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2020 const double sa = sin(s->angle);
2021 const double ca = cos(s->angle);
2041 static const XPoint club[] = {
2052 {-24, 2}, /* 0 close boundary */
2054 XPoint a[XtNumber(club)];
2056 /* Avoid wrapping */
2057 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2059 /* Translate and fake perspective */
2060 for(i = 0; i < XtNumber(club); i++) {
2061 a[i].x = (short)(MI_WIDTH(mi)/2 +
2062 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2063 club[i].y*sqrt(sp->scale)*ca);
2064 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2065 club[i].y*sa*sqrt(sp->scale));
2068 if(color != MI_BLACK_PIXEL(mi)) {
2069 /* Outline in black */
2070 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2071 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2072 LineSolid, CapRound, JoinRound);
2073 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2074 a, XtNumber(a), CoordModeOrigin);
2077 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2079 /* Don't be tempted to optimize erase by drawing all the black in
2080 one X operation. It must use the same ops as the colours to
2081 guarantee a clean erase. */
2083 i = 0; /* Coloured center band */
2084 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2085 p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2086 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2087 p, i, Convex, CoordModeOrigin);
2089 if(color != MI_BLACK_PIXEL(mi)) {
2090 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2093 i = 0; /* White handle */
2094 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2095 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2096 p, i, Convex, CoordModeOrigin);
2098 i = 0; /* White tip */
2099 p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2100 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2101 p, i, Convex, CoordModeOrigin);
2106 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2108 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2109 XPoint head, tail, last;
2110 DXPoint dhead, dlast;
2111 const double sa = sin(s->angle);
2112 const double ca = cos(s->angle);
2114 const double TailLen = -24;
2115 const double HeadLen = 16;
2116 const short Width = (short)(5 * sqrt(sp->scale));
2128 dhead.x = s->x + HeadLen * PERSPEC * sa;
2129 dhead.y = s->y - HeadLen * ca;
2131 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2133 } else { /* Store 'last' so we can use it later when s->prev has
2135 if(s->prev != s->next) {
2136 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2137 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2144 /* Avoid wrapping (after last is stored) */
2145 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2147 head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2148 head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2150 last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2151 last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2153 tail.x = (short)(MI_WIDTH(mi)/2 +
2154 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2155 tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2157 if(color != MI_BLACK_PIXEL(mi)) {
2158 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2159 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2160 Width, LineSolid, CapRound, JoinRound);
2161 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2162 head.x, head.y, tail.x, tail.y);
2164 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2165 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2166 Width * 2, LineSolid, CapRound, JoinRound);
2168 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2169 head.x, head.y, last.x, last.y);
2174 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2176 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2178 const double sa = sin(s->angle);
2179 const double ca = cos(s->angle);
2190 static const XPoint knife[] = {
2198 XPoint a[XtNumber(knife)], p[5];
2200 /* Avoid wrapping */
2201 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2203 /* Translate and fake perspective */
2204 for(i = 0; i < XtNumber(knife); i++) {
2205 a[i].x = (short)(MI_WIDTH(mi)/2 +
2206 (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2207 knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2208 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2209 knife[i].y*sa*sqrt(sp->scale));
2213 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2214 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2215 LineSolid, CapRound, JoinRound);
2216 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2217 a[0].x, a[0].y, a[4].x, a[4].y);
2220 if(color != MI_BLACK_PIXEL(mi)) {
2221 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2224 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2225 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2226 p, i, Convex, CoordModeOrigin);
2230 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2232 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2233 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2234 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2235 double radius = 15 * sp->scale;
2236 short thickness = (short)(8 * sqrt(sp->scale));
2238 /* Avoid wrapping */
2239 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2241 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2242 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2243 thickness, LineSolid, CapRound, JoinRound);
2245 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2246 (short)(x - radius*PERSPEC), (short)(y - radius),
2247 (short)(2*radius*PERSPEC), (short)(2*radius),
2253 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2255 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2256 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2257 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2258 double radius = 12 * sp->scale;
2259 int offset = (int)(s->angle*64*180/M_PI);
2260 int holesize = (int)(3.0*sqrt(sp->scale));
2262 /* Avoid wrapping */
2263 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2265 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2266 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2267 (short)(x - radius), (short)(y - radius),
2268 (short)(2*radius), (short)(2*radius),
2270 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2271 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2272 LineSolid, CapRound, JoinRound);
2273 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2274 (short)(x - radius), (short)(y - radius),
2275 (short)(2*radius), (short)(2*radius),
2278 /* Draw finger holes */
2279 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2280 LineSolid, CapRound, JoinRound);
2282 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2283 (short)(x - radius*0.5), (short)(y - radius*0.5),
2284 (short)(2*radius*0.5), (short)(2*radius*0.5),
2285 (offset + 960) % 23040, 0);
2286 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2287 (short)(x - radius*0.7), (short)(y - radius*0.7),
2288 (short)(2*radius*0.7), (short)(2*radius*0.7),
2289 (offset + 1920) % 23040, 0);
2290 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2291 (short)(x - radius*0.7), (short)(y - radius*0.7),
2292 (short)(2*radius*0.7), (short)(2*radius*0.7),
2296 /**************************************************************************
2297 * Public Functions *
2299 **************************************************************************/
2303 release_juggle(ModeInfo * mi)
2305 if (juggles != NULL) {
2308 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2309 free_juggle(&juggles[screen]);
2311 juggles = (jugglestruct *) NULL;
2313 if (mode_font!=None) {
2314 XFreeFontInfo(NULL,mode_font,1);
2319 /* FIXME: refill_juggle currently just appends new throws to the
2320 * programme. This is fine if the programme is empty, but if there
2321 * are still some trajectories left then it really should take these
2325 refill_juggle(ModeInfo * mi)
2327 jugglestruct *sp = NULL;
2330 if (juggles == NULL)
2332 sp = &juggles[MI_SCREEN(mi)];
2334 /* generate pattern */
2335 if (pattern == NULL) {
2338 #define MAXREPEAT 300
2339 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2340 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2343 while (count < MI_CYCLES(mi)) {
2344 char buf[MAXPAT * 3 + 3], *b = buf;
2346 int l = NRAND(MAXPAT) + 1;
2347 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2349 { /* vary number of balls */
2350 int new_balls = sp->num_balls;
2353 if (new_balls == 2) /* Do not juggle 2 that often */
2354 change = NRAND(2 + CHANGE_BIAS / 4);
2356 change = NRAND(2 + CHANGE_BIAS);
2367 if (new_balls < patternindex.minballs) {
2370 if (new_balls > patternindex.maxballs) {
2373 if (new_balls < sp->num_balls) {
2374 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2377 sp->num_balls = new_balls;
2381 if (NRAND(2) && patternindex.index[sp->num_balls].number) {
2382 /* Pick from PortFolio */
2383 int p = patternindex.index[sp->num_balls].start +
2384 NRAND(patternindex.index[sp->num_balls].number);
2385 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2388 /* Invent a new pattern */
2390 for(i = 0; i < l; i++){
2392 do { /* Triangular Distribution => high values more likely */
2393 m = NRAND(sp->num_balls + 1);
2394 n = NRAND(sp->num_balls + 1);
2396 if (n == sp->num_balls) {
2399 switch(NRAND(5 + POSITION_BIAS)){
2400 case 0: /* Outside throw */
2402 case 1: /* Cross throw */
2404 case 2: /* Cross catch */
2406 case 3: /* Cross throw and catch */
2408 case 4: /* Bounce */
2411 break; /* Inside throw (default) */
2420 if (!program(mi, buf, NULL, t))
2425 } else { /* pattern supplied in height or 'a' notation */
2426 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2443 if (!projectile(sp)) {
2450 if(MI_IS_DEBUG(mi)) dump(sp);
2454 change_juggle(ModeInfo * mi)
2456 jugglestruct *sp = NULL;
2459 if (juggles == NULL)
2461 sp = &juggles[MI_SCREEN(mi)];
2463 /* Strip pending trajectories */
2464 for (t = sp->head->next; t != sp->head; t = t->next) {
2465 if(t->start > sp->time || t->finish < sp->time) {
2468 trajectory_destroy(n);
2472 /* Pick the current object theme */
2473 sp->objtypes = choose_object();
2477 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
2478 don't all those special effects. */
2479 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2481 show_figure(mi, MI_WHITE_PIXEL(mi), True);
2486 /* Used by xscreensaver. xlock just uses init_juggle */
2488 reshape_juggle(ModeInfo * mi, int width, int height)
2495 init_juggle(ModeInfo * mi)
2500 if (only && *only && strcmp(only, " ")) {
2501 balls = clubs = torches = knives = rings = bballs = False;
2502 if (!strcasecmp (only, "balls")) balls = True;
2503 else if (!strcasecmp (only, "clubs")) clubs = True;
2504 else if (!strcasecmp (only, "torches")) torches = True;
2505 else if (!strcasecmp (only, "knives")) knives = True;
2506 else if (!strcasecmp (only, "rings")) rings = True;
2507 else if (!strcasecmp (only, "bballs")) bballs = True;
2509 (void) fprintf (stderr,
2510 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2511 "\t rings, or bballs (not \"%s\")\n", only);
2512 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2518 if (pattern != NULL && *pattern == '.') {
2521 if (pattern == NULL && patternindex.maxballs == 0) {
2522 /* pattern list needs indexing */
2523 int nelements = XtNumber(portfolio);
2526 /* sort according to number of balls */
2527 qsort((void*)portfolio, nelements,
2528 sizeof(portfolio[1]), compare_num_balls);
2530 /* last pattern has most balls */
2531 patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2532 /* run through sorted list, indexing start of each group
2533 and number in group */
2534 patternindex.maxballs = 1;
2535 for (i = 0; i < nelements; i++) {
2536 int b = get_num_balls(portfolio[i].pattern);
2537 if (b > patternindex.maxballs) {
2538 patternindex.index[patternindex.maxballs].number = numpat;
2539 if(numpat == 0) patternindex.minballs = b;
2540 patternindex.maxballs = b;
2542 patternindex.index[patternindex.maxballs].start = i;
2547 patternindex.index[patternindex.maxballs].number = numpat;
2550 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
2551 only be resizing and then we won't all those special effects. */
2552 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2554 if (juggles == NULL) { /* First-time initialisation */
2556 /* allocate jugglestruct */
2558 (jugglestruct *)calloc(MI_NUM_SCREENS(mi),
2559 sizeof (jugglestruct))) == NULL) {
2564 sp = &juggles[MI_SCREEN(mi)];
2566 sp->count = ABS(MI_COUNT(mi));
2570 /* record start time */
2571 sp->begintime = time(NULL);
2572 if(patternindex.maxballs > 0) {
2573 sp->num_balls = patternindex.minballs +
2574 NRAND(patternindex.maxballs - patternindex.minballs);
2577 show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
2578 information about the juggler's
2581 /* "7" should be about three times the height of the juggler's
2583 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2584 7 * THROW_CATCH_INTERVAL);
2586 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2587 balls = True; /* Have to juggle something! */
2589 /* create circular trajectory list */
2590 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2591 if(sp->head == NULL){
2596 /* create circular object list */
2597 ADD_ELEMENT(Object, sp->objects, sp->objects);
2598 if(sp->objects == NULL){
2603 /* create circular wander list */
2604 ADD_ELEMENT(Wander, sp->wander, sp->wander);
2605 if(sp->wander == NULL){
2609 (void)wander(sp, 0); /* Initialize wander */
2611 sp->pattern = strdup(""); /* Initialise saved pattern with
2614 /* Set up programme */
2619 /* Only put things here that won't interrupt the programme during
2622 sp = &juggles[MI_SCREEN(mi)];
2624 /* Use MIN so that users can resize in interesting ways, eg
2625 narrow windows for tall patterns, etc */
2626 sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2628 if(describe && mode_font == None) { /* Check to see if there's room to describe patterns. */
2629 mode_font = XQueryFont(MI_DISPLAY(mi), XGContextFromGC(MI_GC(mi)));
2634 draw_juggle(ModeInfo * mi)
2636 Trajectory *traj = NULL;
2638 unsigned long future = 0;
2639 jugglestruct *sp = NULL;
2640 char *pattern = NULL;
2643 if (juggles == NULL)
2645 sp = &juggles[MI_SCREEN(mi)];
2647 MI_IS_DRAWN(mi) = True;
2652 (void)gettimeofday(&tv, NULL);
2653 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2655 sp->time += MI_DELAY(mi) / 1000;
2658 /* First pass: Move arms and strip out expired elements */
2659 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2660 if (traj->status != PREDICTOR) {
2661 /* Skip any elements that need further processing */
2662 /* We could remove them, but there shoudn't be many and they
2663 would be needed if we ever got the pattern refiller
2667 if (traj->start > future) { /* Lookahead to the end of the show */
2668 future = traj->start;
2670 if (sp->time < traj->start) { /* early */
2672 } else if (sp->time < traj->finish) { /* working */
2674 /* Look for pattern name */
2675 if(traj->pattern != NULL) {
2676 pattern=traj->pattern;
2679 if (traj->type == Empty || traj->type == Full) {
2680 /* Only interested in hands on this pass */
2681 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2682 double xd = 0, yd = 0;
2685 /* Find the catching offset */
2686 if(traj->object != NULL) {
2687 if(ObjectDefs[traj->object->type].handle > 0) {
2688 /* Handles Need to be oriented */
2689 xd = ObjectDefs[traj->object->type].handle *
2690 PERSPEC * sin(angle);
2691 yd = ObjectDefs[traj->object->type].handle *
2694 /* Balls are always caught at the bottom */
2699 p.x = (CUBIC(traj->xp, sp->time) - xd);
2700 p.y = (CUBIC(traj->yp, sp->time) + yd);
2701 reach_arm(mi, traj->hand, &p);
2703 /* Store updated hand position */
2707 if (traj->type == Ball || traj->type == Full) {
2708 /* Only interested in objects on this pass */
2712 if(traj->type == Full) {
2713 /* Adjusted these in the first pass */
2717 x = CUBIC(traj->xp, sp->time);
2718 y = CUBIC(traj->yp, sp->time);
2721 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2724 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2725 s->divisions = traj->divisions;
2726 traj->object->tracelen++;
2727 traj->object->active = True;
2729 } else { /* expired */
2730 Trajectory *n = traj;
2732 trajectory_destroy(n);
2736 /* Erase end of trails */
2737 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2739 for (s = o->trace->next;
2740 o->trace->next != o->trace &&
2741 (o->count == 0 || o->tracelen > o->tail);
2742 s = o->trace->next) {
2743 ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2746 if(o->count <= 0 && o->tracelen <= 0) {
2747 /* Object no longer in use and trail gone */
2752 if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2756 show_arms(mi, MI_BLACK_PIXEL(mi));
2757 cx = wander(sp, sp->time);
2758 /* Reduce flicker by only permitting movements of more than a pixel */
2759 if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2760 show_figure(mi, MI_BLACK_PIXEL(mi), False);
2764 show_figure(mi, MI_WHITE_PIXEL(mi), False);
2766 show_arms(mi, MI_WHITE_PIXEL(mi));
2769 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2771 ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2777 /* Save pattern name so we can erase it when it changes */
2778 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2779 /* Erase old name */
2780 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2781 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2782 0, 20, sp->pattern, strlen(sp->pattern));
2784 sp->pattern = strdup(pattern);
2786 if (MI_IS_VERBOSE(mi)) {
2787 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2788 MI_SCREEN(mi), sp->pattern);
2791 if(mode_font != None &&
2792 XTextWidth(mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2793 /* Redraw once a cycle, in case it's obscured or it changed */
2794 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2795 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2796 0, 20, sp->pattern, strlen(sp->pattern));
2800 if((int)(sp->time/10) % 1000 == 0)
2801 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2804 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2806 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2812 #endif /* MODE_juggle */