1 /* -*- Mode: C; tab-width: 4 -*- */
5 static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore";
9 * Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
11 * Permission to use, copy, modify, and distribute this software and its
12 * documentation for any purpose and without fee is hereby granted,
13 * provided that the above copyright notice appear in all copies and that
14 * both that copyright notice and this permission notice appear in
15 * supporting documentation.
17 * This file is provided AS IS with no warranties of any kind. The author
18 * shall have no liability with respect to the infringement of copyrights,
19 * trade secrets or any patents by this file or any part thereof. In no
20 * event will the author be liable for any lost revenue or profits or
21 * other special, indirect and consequential damages.
24 * 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
25 * Add -rings, -bballs. Add -describe. Finally made
26 * live pattern updates possible. Add refill_juggle(),
27 * change_juggle() and reshape_juggle(). Make
28 * init_juggle() non-destructive. Reorder erase/draw
29 * operations. Update xscreensaver xml and manpage.
30 * 15-Nov-2004: [TDA] Fix all memory leaks.
31 * 12-Nov-2004: [TDA] Add -torches and another new trail
32 * implementation, so that different objects can have
33 * different length trails.
34 * 11-Nov-2004: [TDA] Clap when all the balls are in the air.
35 * 10-Nov-2004: [TDA] Display pattern name converted to hight
37 * 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
38 * 02-Sep-2003: Non-real time to see what is happening without a
39 * strobe effect for slow machines.
40 * 01-Nov-2000: Allocation checks
46 * Implement the anonymously promised -uni option.
51 * Notes on Adam Chalcraft Juggling Notation (used by permission)
52 * a-> Adam's notation s-> Site swap (Cambridge) notation
54 * To define a map from a-notation to s-notation ("site-swap"), both
55 * of which look like doubly infinite sequences of natural numbers. In
56 * s-notation, there is a restriction on what is allowed, namely for
57 * the sequence s_n, the associated function f(n)=n+s_n must be a
58 * bijection. In a-notation, there is no restriction.
60 * To go from a-notation to s-notation, you start by mapping each a_n
61 * to a permutation of N, the natural numbers.
64 * 1 -> (10) [i.e. f(1)=0, f(0)=1]
65 * 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
66 * 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
69 * Then for each n, you look at how long 0 takes to get back to 0
70 * again and you call this t_n. If a_n=0, for example, then since the
71 * identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
72 * a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
73 * next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
74 * following the 1. Finally, set s_n = t_n - 1.
76 * To give some examples, it helps to have a notation for cyclic
77 * sequences. By (123), for example, I mean ...123123123123... . Now
78 * under the a-notation -> s-notation mapping we have some familiar
81 * (0)->(0), (1)->(1), (2)->(2) etc.
82 * (21)->(31), (31)->(51), (41)->(71) etc.
83 * (10)->(20), (20)->(40), (30)->(60) etc.
84 * (331)->(441), (312)->(612), (303)->(504), (321)->(531)
85 * (43)->(53), (434)->(534), (433)->(633)
88 * In general, the number of balls is the *average* of the s-notation,
89 * and the *maximum* of the a-notation. Another theorem is that the
90 * minimum values in the a-notation and the s-notation and equal, and
91 * preserved in the same positions.
93 * The usefulness of a-notation is the fact that there are no
94 * restrictions on what is allowed. This makes random juggle
95 * generation much easier. It also makes enumeration very
96 * easy. Another handy feature is computing changes. Suppose you can
97 * do (5) and want a neat change up to (771) in s-notation [Mike Day
98 * actually needed this example!]. Write them both in a-notation,
99 * which gives (5) and (551). Now concatenate them (in general, there
100 * may be more than one way to do this, but not in this example), to
103 * ...55555555551551551551551...
105 * Now convert back to s-notation, to get
107 * ...55555566771771771771771...
109 * So the answer is to do two 6 throws and then go straight into
110 * (771). Coming back down of course,
112 * ...5515515515515515555555555...
116 * ...7717717717716615555555555...
118 * so the answer is to do a single 661 and then drop straight down to
121 * [The number of balls in the generated pattern occasionally changes.
122 * In order to decrease the number of balls I had to introduce a new
123 * symbol into the Adam notation, [*] which means 'lose the current
127 /* This code uses so many linked lists it's worth having a built-in
133 # define DEFAULTS "*delay: 10000 \n" \
137 "*font: -*-helvetica-bold-r-normal-*-180-*\n" \
138 "*fpsSolid: true\n" \
140 # define refresh_juggle 0
141 # define juggle_handle_event 0
142 # undef SMOOTH_COLORS
143 # include "xlockmore.h" /* in xscreensaver distribution */
144 #else /* STANDALONE */
145 # include "xlock.h" /* in xlockmore distribution */
146 #endif /* STANDALONE */
151 #define XClearWindow(d, w) \
153 XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \
154 XFillRectangle(d, w, MI_GC(mi), \
155 0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \
159 #define DEF_PATTERN "random" /* All patterns */
160 #define DEF_TAIL "1" /* No trace */
162 /* Maybe a ROLA BOLA would be at a better angle for viewing */
163 #define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
165 #define DEF_REAL "True"
166 #define DEF_DESCRIBE "True"
168 #define DEF_BALLS "True" /* Use Balls */
169 #define DEF_CLUBS "True" /* Use Clubs */
170 #define DEF_TORCHES "True" /* Use Torches */
171 #define DEF_KNIVES "True" /* Use Knives */
172 #define DEF_RINGS "True" /* Use Rings */
173 #define DEF_BBALLS "True" /* Use Bowling Balls */
176 #define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
179 static char *pattern;
185 static Bool describe;
194 static XrmOptionDescRec opts[] =
196 {"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
197 {"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
199 {"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
200 {"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
202 {"-real", ".juggle.real", XrmoptionNoArg, "on" },
203 {"+real", ".juggle.real", XrmoptionNoArg, "off" },
204 {"-describe", ".juggle.describe", XrmoptionNoArg, "on" },
205 {"+describe", ".juggle.describe", XrmoptionNoArg, "off" },
206 {"-balls", ".juggle.balls", XrmoptionNoArg, "on" },
207 {"+balls", ".juggle.balls", XrmoptionNoArg, "off" },
208 {"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" },
209 {"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" },
210 {"-torches", ".juggle.torches", XrmoptionNoArg, "on" },
211 {"+torches", ".juggle.torches", XrmoptionNoArg, "off" },
212 {"-knives", ".juggle.knives", XrmoptionNoArg, "on" },
213 {"+knives", ".juggle.knives", XrmoptionNoArg, "off" },
214 {"-rings", ".juggle.rings", XrmoptionNoArg, "on" },
215 {"+rings", ".juggle.rings", XrmoptionNoArg, "off" },
216 {"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" },
217 {"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" },
218 {"-only", ".juggle.only", XrmoptionSepArg, NULL },
220 static argtype vars[] =
222 { &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
223 { &tail, "tail", "Tail", DEF_TAIL, t_Int },
225 { &uni, "uni", "Uni", DEF_UNI, t_Bool },
227 { &real, "real", "Real", DEF_REAL, t_Bool },
228 { &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool },
229 { &balls, "balls", "Clubs", DEF_BALLS, t_Bool },
230 { &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool },
231 { &torches, "torches", "Torches", DEF_TORCHES, t_Bool },
232 { &knives, "knives", "Knives", DEF_KNIVES, t_Bool },
233 { &rings, "rings", "Rings", DEF_RINGS, t_Bool },
234 { &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool },
235 { &only, "only", "BBalls", " ", t_String },
237 static OptionStruct desc[] =
239 { "-pattern string", "Cambridge Juggling Pattern" },
240 { "-tail num", "Trace Juggling Patterns" },
242 { "-/+uni", "Unicycle" },
244 { "-/+real", "Real-time" },
245 { "-/+describe", "turn on/off pattern descriptions." },
246 { "-/+balls", "turn on/off Balls." },
247 { "-/+clubs", "turn on/off Clubs." },
248 { "-/+torches", "turn on/off Flaming Torches." },
249 { "-/+knives", "turn on/off Knives." },
250 { "-/+rings", "turn on/off Rings." },
251 { "-/+bballs", "turn on/off Bowling Balls." },
252 { "-only", "Turn off all objects but the named one." },
255 ENTRYPOINT ModeSpecOpt juggle_opts =
256 { XtNumber(opts), opts, XtNumber(vars), vars, desc };
259 ModStruct juggle_description = {
260 "juggle", "init_juggle", "draw_juggle", "release_juggle",
261 "draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
262 10000, 200, 1000, 1, 64, 1.0, "",
263 "Shows a Juggler, juggling", 0, NULL
269 # include <X11/unix_time.h>
272 /* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All
273 "thicknesses" are scaled by sqrt(sp->scale) so that they are
274 proportionally thicker for smaller windows. Objects spinning out
275 of the plane (such as clubs) fake perspective by compressing their
276 horizontal coordinates by PERSPEC */
280 #define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
282 #define BALLRADIUS ARMWIDTH
287 #define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
289 /* Timing based on count. Units are milliseconds. Juggles per second
290 is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
292 #define THROW_CATCH_INTERVAL (sp->count)
293 #define THROW_NULL_INTERVAL (sp->count * 0.5)
294 #define CATCH_THROW_INTERVAL (sp->count * 0.2)
296 /********************************************************************
297 * Trace Definitions *
299 * These record rendering data so that a drawn object can be erased *
300 * later. Each object has its own Trace list. *
302 ********************************************************************/
304 typedef struct {double x, y; } DXPoint;
305 typedef struct trace *TracePtr;
306 typedef struct trace {
317 /*******************************************************************
318 * Object Definitions *
320 * These describe the various types of Object that can be juggled *
322 *******************************************************************/
323 typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
325 static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
326 static DrawProc show_ring, show_bball;
328 typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
329 NUM_OBJECT_TYPES} ObjType;
330 #define OBJMIXPROB 20 /* inverse of the chances of using an odd
331 object in the pattern */
333 static const struct {
334 DrawProc *draw; /* Object Rendering function */
335 int handle; /* Length of object's handle */
336 int mintrail; /* Minimum trail length */
337 double cor; /* Coefficient of Restitution. perfect bounce = 1 */
338 double weight; /* Heavier objects don't get thrown as high */
351 0.55, /* Clubs don't bounce too well */
357 20, /* Torches need flames */
358 0, /* Torches don't bounce -- fire risk! */
365 0, /* Knives don't bounce */
384 /**************************
385 * Trajectory definitions *
386 **************************/
388 typedef enum {HEIGHT, ADAM} Notation;
389 typedef enum {Empty, Full, Ball} Throwable;
390 typedef enum {LEFT, RIGHT} Hand;
391 typedef enum {THROW, CATCH} Action;
392 typedef enum {HAND, ELBOW, SHOULDER} Joint;
393 typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
394 PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
395 typedef struct {double a, b, c, d; } Spline;
396 typedef DXPoint Arm[3];
398 /* A Wander contains a Spline and a time interval. A list of Wanders
399 * describes the performer's position as he moves around the screen. */
400 typedef struct wander *WanderPtr;
401 typedef struct wander {
402 WanderPtr next, prev;
404 unsigned long finish;
411 /* Object is an arbitrary object being juggled. Each Trajectory
412 * references an Object ("count" tracks this), and each Object is also
413 * linked into a global Objects list. Objects may include a Trace
414 * list for tracking erasures. */
415 typedef struct object *ObjectPtr;
416 typedef struct object {
417 ObjectPtr next, prev;
421 int count; /* reference count */
422 Bool active; /* Object is in use */
432 /* Trajectory is a segment of juggling action. A list of Trajectories
433 * defines the juggling performance. The Trajectory list goes through
434 * multiple processing steps to convert it from basic juggling
435 * notation into rendering data. */
437 typedef struct trajectory *TrajectoryPtr;
438 typedef struct trajectory {
439 TrajectoryPtr prev, next; /* for building list */
440 TrajectoryStatus status;
458 TrajectoryPtr balllink;
459 TrajectoryPtr handlink;
462 double cx; /* Moving juggler */
463 double x, y; /* current position */
464 double dx, dy; /* initial velocity */
468 unsigned long start, finish;
482 const char * pattern;
486 /* List of popular patterns, in any order */
487 /* Patterns should be given in Adam notation so the generator can
488 concatenate them safely. Null descriptions are ok. Height
489 notation will be displayed automatically. */
490 /* Can't const this because it is qsorted. This *should* be reentrant,
492 static /*const*/ patternstruct portfolio[] = {
493 {"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
494 {"[2 0]", /* 4 0 */ "2 in 1 hand"},
495 {"[2 0 1]", /* 5 0 1 */},
496 {"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
497 {"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
498 {"[2 0 1 1]", /* 6 0 1 1 */},
500 {"[3]", /* 3 */ "3 cascade"},
501 {"[+3]", /* +3 */ "reverse 3 cascade"},
502 {"[=3]", /* =3 */ "cascade 3 under arm"},
503 {"[&3]", /* &3 */ "cascade 3 catching under arm"},
504 {"[_3]", /* _3 */ "bouncing 3 cascade"},
505 {"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
506 {"[3 2 1]", /* 5 3 1" */},
507 {"[3 3 1]", /* 4 4 1" */},
508 {"[3 1 2]", /* 6 1 2 */ "See-saw"},
509 {"[=3 3 1 2]", /* =4 5 1 2 */},
510 {"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
511 {"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
512 {"[3 3 1]", /* 4 4 1 */},
513 {"[+3 2 3]", /* +4 2 3 */},
514 {"[+3 1]", /* +5 1 */ "3 shower"},
515 {"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
516 {"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
517 {"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
518 {"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
519 {"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
520 {"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
521 {"[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 */},
522 {"[3 0 2 0]", /* 8 0 4 0 */},
523 {"[_3 2 1]", /* _5 3 1 */},
524 {"[_3 0 1]", /* _8 0 1 */},
525 {"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
526 {"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
528 {"[4]", /* 4 */ "4 cascade"},
529 {"[+4 3]", /* +5 3 */ "4 ball half shower"},
530 {"[4 4 2]", /* 5 5 2 */},
531 {"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
532 {"[+4 3 +4]", /* +5 3 +4 */},
533 {"[4 3 4 4]", /* 5 3 4 4 */},
534 {"[4 3 3 4]", /* 6 3 3 4 */},
535 {"[4 3 2 4", /* 6 4 2 4 */},
536 {"[+4 1]", /* +7 1 */ "4 shower"},
537 {"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
538 {"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
539 {"[+4 2 1 3]", /* +9 3 1 3 */},
540 {"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
541 {"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
542 {"[_4 3 3]", /* _6 3 3 */},
543 {"[_4 3 1]", /* _7 4 1 */},
544 {"[_4 2 1]", /* _8 3 1 */},
545 {"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
546 {"[_4 1 3 1]", /* _9 1 5 1 */},
547 {"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
549 {"[5]", /* 5 */ "5 cascade"},
550 {"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
551 {"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
552 {"[5 4 4]", /* 7 4 4 */},
553 {"[_5 4 4]", /* _7 4 4 */},
554 {"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
555 {"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
556 {"[_5 4 1 +4]", /* _9 5 1 5 */},
557 {"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
558 {"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
560 {"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
562 {"[6]", /* 6 */ "6 cascade"},
563 {"[+6 5]", /* +7 5 */},
564 {"[6 4]", /* 8 4 */},
565 {"[+6 3]", /* +9 3 */},
566 {"[6 5 4 4]", /* 9 7 4 4 */},
567 {"[+6 5 5 5]", /* +9 5 5 5 */},
568 {"[6 0 6]", /* 9 0 9 */},
569 {"[_6 0 _6]", /* _9 0 _9 */},
571 {"[_7]", /* _7 */ "bouncing 7 cascade"},
572 {"[7]", /* 7 */ "7 cascade"},
573 {"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
579 typedef struct { int start; int number; } PatternIndex;
581 struct patternindex {
584 PatternIndex index[XtNumber(portfolio)];
588 /* Jugglestruct: per-screen global data. The master Wander, Object
589 * and Trajectory lists are anchored here. */
600 time_t begintime; /* should make 'time' usable for at least 48 days
601 on a 32-bit machine */
602 unsigned long time; /* millisecond timer*/
605 struct patternindex patternindex;
606 XFontStruct *mode_font;
609 static jugglestruct *juggles = (jugglestruct *) NULL;
615 #define DUP_OBJECT(n, t) { \
616 (n)->object = (t)->object; \
617 if((n)->object != NULL) (n)->object->count++; \
620 /* t must point to an existing element. t must not be an
621 expression ending ->next or ->prev */
622 #define REMOVE(t) { \
623 (t)->next->prev = (t)->prev; \
624 (t)->prev->next = (t)->next; \
628 /* t receives element to be created and added to the list. ot must
629 point to an existing element or be identical to t to start a new
630 list. Applicable to Trajectories, Objects and Traces. */
631 #define ADD_ELEMENT(type, t, ot) \
632 if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
633 (t)->next = (ot)->next; \
636 (t)->next->prev = (t); \
640 object_destroy(Object* o)
642 if(o->trace != NULL) {
643 while(o->trace->next != o->trace) {
644 Trace *s = o->trace->next;
645 REMOVE(s); /* Don't eliminate 's' */
653 trajectory_destroy(Trajectory *t) {
654 if(t->name != NULL) free(t->name);
655 if(t->pattern != NULL) free(t->pattern);
656 /* Reduce object link count and call destructor if necessary */
657 if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
658 object_destroy(t->object);
660 REMOVE(t); /* Unlink and free */
664 free_juggle(jugglestruct *sp) {
665 if (sp->head != NULL) {
666 while (sp->head->next != sp->head) {
667 trajectory_destroy(sp->head->next);
670 sp->head = (Trajectory *) NULL;
672 if(sp->objects != NULL) {
673 while (sp->objects->next != sp->objects) {
674 object_destroy(sp->objects->next);
677 sp->objects = (Object*)NULL;
679 if(sp->wander != NULL) {
680 while (sp->wander->next != sp->wander) {
681 Wander *w = sp->wander->next;
685 sp->wander = (Wander*)NULL;
687 if(sp->pattern != NULL) {
691 if (sp->mode_font!=None) {
692 XFreeFontInfo(NULL,sp->mode_font,1);
693 sp->mode_font = None;
698 add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
702 ADD_ELEMENT(Trajectory, t, sp->head->prev);
703 if(t == NULL){ /* Out of Memory */
709 t->name = strdup(name);
722 /* add a Thratch to the performance */
724 program(ModeInfo *mi, const char *patn, const char *name, int cycles)
726 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
732 if (MI_IS_VERBOSE(mi)) {
733 (void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
734 MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
737 for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
738 have been programmed */
739 /* title is the pattern name to be supplied to the first throw of
740 a sequence. If no name if given, use an empty title so that
741 the sequences are still delimited. */
742 const char *title = (name != NULL)? name : "";
747 for(p=patn; *p; p++) {
748 if (*p >= '0' && *p <='9') {
750 h = 10*h + (*p - '0');
752 Notation nn = notation;
754 case '[': /* begin Adam notation */
757 case '-': /* Inside throw */
760 case '+': /* Outside throw */
761 case '=': /* Cross throw */
762 case '&': /* Cross catch */
763 case 'x': /* Cross throw and catch */
764 case '_': /* Bounce */
765 case 'k': /* Kickup */
768 case '*': /* Lose ball */
772 case ']': /* end Adam notation */
778 if (!add_throw(sp, type, h, notation, title))
788 if(w == 0) { /* Only warn on first pass */
789 (void) fprintf(stderr,
790 "juggle[%d]: Unexpected pattern instruction: '%c'\n",
797 if (seen) { /* end of sequence */
798 if (!add_throw(sp, type, h, notation, title))
812 [ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
814 4 4 1 3 12 2 4 1 4 4 13 0 3 1
817 #define BOUNCEOVER 10
821 /* Convert Adam notation into heights */
823 adam(jugglestruct *sp)
826 for(t = sp->head->next; t != sp->head; t = t->next) {
827 if (t->status == ATCH) {
830 for(p = t->next; a > 0; p = p->next) {
832 t->height = -9; /* Indicate end of processing for name() */
835 if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
840 if(t->height > BOUNCEOVER && t->posn == ' '){
841 t->posn = '_'; /* high defaults can be bounced */
842 } else if(t->height < 3 && t->posn == '_') {
843 t->posn = ' '; /* Can't bounce short throws. */
845 if(t->height < KICKMIN && t->posn == 'k'){
846 t->posn = ' '; /* Can't kick short throws */
848 if(t->height > THROWMAX){
849 t->posn = 'k'; /* Use kicks for ridiculously high throws */
856 /* Discover converted heights and update the sequence title */
858 name(jugglestruct *sp)
863 for(t = sp->head->next; t != sp->head; t = t->next) {
864 if (t->status == THRATCH && t->name != NULL) {
866 for(p = t; p == t || p->name == NULL; p = p->next) {
867 if(p == sp->head || p->height < 0) { /* end of reliable data */
871 b += sprintf(b, " %d", p->height);
873 b += sprintf(b, " %c%d", p->posn, p->height);
875 if(b - buffer > 500) break; /* otherwise this could eventually
876 overflow. It'll be too big to
880 (void) sprintf(b, ", %s", t->name);
882 free(t->name); /* Don't need name any more, it's been converted
885 if(t->pattern != NULL) free(t->pattern);
886 t->pattern = strdup(buffer);
891 /* Split Thratch notation into explicit throws and catches.
892 Usually Catch follows Throw in same hand, but take care of special
895 /* ..n1.. -> .. LTn RT1 LC RC .. */
896 /* ..nm.. -> .. LTn LC RTm RC .. */
899 part(jugglestruct *sp)
901 Trajectory *t, *nt, *p;
902 Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
904 for (t = sp->head->next; t != sp->head; t = t->next) {
905 if (t->status > THRATCH) {
907 } else if (t->status == THRATCH) {
910 /* plausibility check */
911 if (t->height <= 2 && t->posn == '_') {
912 t->posn = ' '; /* no short bounces */
914 if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
915 t->posn = ' '; /* 1's need close catches */
920 case ' ': posn = '-'; t->posn = '+'; break;
921 case '+': posn = '+'; t->posn = '-'; break;
922 case '=': posn = '='; t->posn = '+'; break;
923 case '&': posn = '+'; t->posn = '='; break;
924 case 'x': posn = '='; t->posn = '='; break;
925 case '_': posn = '_'; t->posn = '-'; break;
926 case 'k': posn = 'k'; t->posn = 'k'; break;
928 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
931 hand = (Hand) ((hand + 1) % 2);
936 if (t->height == 1 && p != sp->head) {
937 p = p->prev; /* '1's are thrown earlier than usual */
943 ADD_ELEMENT(Trajectory, nt, p);
951 nt->height = t->height;
961 choose_object(void) {
964 o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
965 if(balls && o == BALL) break;
966 if(clubs && o == CLUB) break;
967 if(torches && o == TORCH) break;
968 if(knives && o == KNIFE) break;
969 if(rings && o == RING) break;
970 if(bballs && o == BBALLS) break;
975 /* Connnect up throws and catches to figure out which ball goes where.
976 Do the same with the juggler's hands. */
981 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
984 for (t = sp->head->next; t != sp->head; t = t->next) {
985 if (t->status == ACTION) {
986 if (t->action == THROW) {
987 if (t->type == Empty) {
988 /* Create new Object */
989 ADD_ELEMENT(Object, t->object, sp->objects);
990 t->object->count = 1;
991 t->object->tracelen = 0;
992 t->object->active = False;
993 /* Initialise object's circular trace list */
994 ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
996 if (MI_NPIXELS(mi) > 2) {
997 t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
1000 t->object->color = 1;
1002 t->object->color = 0;
1006 /* Small chance of picking a random object instead of the
1008 if(NRAND(OBJMIXPROB) == 0) {
1009 t->object->type = choose_object();
1011 t->object->type = sp->objtypes;
1014 /* Check to see if we need trails for this object */
1015 if(tail < ObjectDefs[t->object->type].mintrail) {
1016 t->object->tail = ObjectDefs[t->object->type].mintrail;
1018 t->object->tail = tail;
1022 /* Balls can change divisions at each throw */
1023 t->divisions = 2 * (NRAND(2) + 1);
1025 /* search forward for next catch in this hand */
1026 for (p = t->next; t->handlink == NULL; p = p->next) {
1027 if(p->status < ACTION || p == sp->head) return;
1028 if (p->action == CATCH) {
1029 if (t->handlink == NULL && p->hand == t->hand) {
1035 if (t->height > 0) {
1038 /* search forward for next ball catch */
1039 for (p = t->next; t->balllink == NULL; p = p->next) {
1040 if(p->status < ACTION || p == sp->head) {
1044 if (p->action == CATCH) {
1045 if (t->balllink == NULL && --h < 1) { /* caught */
1046 t->balllink = p; /* complete trajectory */
1048 if (p->type == Full) {
1049 (void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
1050 MI_SCREEN(mi), t->object->color);
1054 DUP_OBJECT(p, t); /* accept catch */
1055 p->angle = t->angle;
1056 p->divisions = t->divisions;
1061 t->type = Empty; /* thrown */
1062 } else if (t->action == CATCH) {
1063 /* search forward for next throw from this hand */
1064 for (p = t->next; t->handlink == NULL; p = p->next) {
1065 if(p->status < ACTION || p == sp->head) return;
1066 if (p->action == THROW && p->hand == t->hand) {
1067 p->type = t->type; /* pass ball */
1068 DUP_OBJECT(p, t); /* pass object */
1069 p->divisions = t->divisions;
1074 t->status = LINKEDACTION;
1079 /* Clap when both hands are empty */
1081 clap(jugglestruct *sp)
1084 for (t = sp->head->next; t != sp->head; t = t->next) {
1085 if (t->status == LINKEDACTION &&
1086 t->action == CATCH &&
1088 t->handlink != NULL &&
1089 t->handlink->height == 0) { /* Completely idle hand */
1091 for (p = t->next; p != sp->head; p = p->next) {
1092 if (p->status == LINKEDACTION &&
1093 p->action == CATCH &&
1094 p->hand != t->hand) { /* Next catch other hand */
1095 if(p->type == Empty &&
1096 p->handlink != NULL &&
1097 p->handlink->height == 0) { /* Also completely idle */
1099 t->handlink->posn = '^'; /* Move first hand's empty throw */
1100 p->posn = '^'; /* to meet second hand's empty
1104 break; /* Only need first catch */
1111 #define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
1113 /* Compute single spline from x0 with velocity dx0 at time t0 to x1
1114 with velocity dx1 at time t1 */
1116 makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
1125 a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
1126 b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
1131 s.c = (3*a*t0 - 2*b)*t0 + c;
1132 s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
1136 /* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
1137 time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
1138 velocity dx2 at time t2. The arrival and departure velocities at
1139 x1, t1 must be the same. */
1141 makeSplinePair(Spline *s1, Spline *s2,
1142 double x0, double dx0, int t0,
1144 double x2, double dx2, int t2)
1146 double x10, x21, t21, t10, t20, dx1;
1152 dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
1153 - dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
1155 *s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
1156 *s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
1160 /* Compute a Ballistic path in a pair of degenerate splines. sx goes
1161 from x at time t at constant velocity dx. sy goes from y at time t
1162 with velocity dy and constant acceleration g. */
1164 makeParabola(Trajectory *n,
1165 double x, double dx, double y, double dy, double g)
1167 double t = (double)n->start;
1171 n->xp.d = -dx*t + x;
1174 n->yp.c = -g*t + dy;
1175 n->yp.d = g/2*t*t - dy*t + y;
1180 /* Make juggler wander around the screen */
1181 static double wander(jugglestruct *sp, unsigned long time)
1184 for (w = sp->wander->next; w != sp->wander; w = w->next) {
1185 if (w->finish < sp->time) { /* expired */
1189 } else if(w->finish > time) {
1193 if(w == sp->wander) { /* Need a new one */
1194 ADD_ELEMENT(Wander, w, sp->wander->prev);
1195 if(w == NULL) { /* Memory problem */
1198 w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
1202 w->x = w->prev->x * 0.9 + NRAND(40) - 20;
1204 w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
1206 return CUBIC(w->s, time);
1209 #define SX 25 /* Shoulder Width */
1211 /* Convert hand position symbols into actual time/space coordinates */
1213 positions(jugglestruct *sp)
1216 unsigned long now = sp->time; /* Make sure we're not lost in the past */
1217 for (t = sp->head->next; t != sp->head; t = t->next) {
1218 if (t->status >= PTHRATCH) {
1220 } else if (t->status == ACTION || t->status == LINKEDACTION) {
1221 /* Allow ACTIONs to be annotated, but we won't mark them ready
1222 for the next stage */
1229 if (t->action == CATCH) { /* Throw-to-catch */
1230 if (t->type == Empty) {
1231 now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
1232 } else { /* successful catch */
1233 now += (int)(THROW_CATCH_INTERVAL);
1235 } else { /* Catch-to-throw */
1236 if(t->object != NULL) {
1237 now += (int) (CATCH_THROW_INTERVAL *
1238 ObjectDefs[t->object->type].weight);
1240 now += (int) (CATCH_THROW_INTERVAL);
1246 else /* Concatenated performances may need clock resync */
1249 t->cx = wander(sp, t->start);
1254 /* Add room for the handle */
1255 if(t->action == CATCH && t->object != NULL)
1256 yo -= ObjectDefs[t->object->type].handle;
1259 case '-': xo = sx - pose; break;
1262 case '+': xo = sx + pose; break;
1264 case '=': xo = - sx - pose; yo += pose; break;
1265 case '^': xo = 0; yo += pose*2; break; /* clap */
1267 (void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
1271 t->angle = (((t->hand == LEFT) ^
1272 (t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
1275 t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
1278 /* Only mark complete if it was already linked */
1279 if(t->status == LINKEDACTION) {
1280 t->status = PTHRATCH;
1287 /* Private physics functions */
1289 /* Compute the spin-rate for a trajectory. Different types of throw
1290 (eg, regular thows, bounces, kicks, etc) have different spin
1293 type = type of object
1294 h = trajectory of throwing hand (throws), or next throwing hand (catches)
1295 old = earlier spin to consider
1296 dt = time span of this trajectory
1297 height = height of ball throw or 0 if based on old spin
1298 turns = full club turns required during this operation
1299 togo = partial club turns required to match hands
1302 spinrate(ObjType type, Trajectory *h, double old, double dt,
1303 int height, int turns, double togo)
1305 const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
1307 if(ObjectDefs[type].handle != 0) { /* Clubs */
1308 return (dir * turns * 2 * M_PI + togo) / dt;
1309 } else if(height == 0) { /* Balls already spinning */
1311 } else { /* Balls */
1312 return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
1317 /* compute the angle at the end of a spinning trajectory */
1319 end_spin(Trajectory *t)
1321 return t->angle + t->spin * (t->finish - t->start);
1324 /* Sets the initial angle of the catch following hand movement t to
1325 the final angle of the throw n. Also sets the angle of the
1326 subsequent throw to the same angle plus half a turn. */
1328 match_spins_on_catch(Trajectory *t, Trajectory *n)
1330 if(ObjectDefs[t->balllink->object->type].handle == 0) {
1331 t->balllink->angle = end_spin(n);
1332 if(t->balllink->handlink != NULL) {
1333 t->balllink->handlink->angle = t->balllink->angle + M_PI;
1339 find_bounce(jugglestruct *sp,
1340 double yo, double yf, double yc, double tc, double cor)
1342 double tb, i, dy = 0;
1343 const double e = 1; /* permissible error in yc */
1347 yt = height at catch time after one bounce
1348 one or three roots according to timing
1349 find one by interval bisection
1352 for(i = tc / 2; i > 0.0001; i/=2){
1355 (void) fprintf(stderr, "juggle: bounce div by zero!\n");
1358 dy = (yf - yo)/tb + sp->Gr/2*tb;
1360 yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
1363 }else if(yt > yc - e){
1369 if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
1376 new_predictor(const Trajectory *t, int start, int finish, double angle)
1379 ADD_ELEMENT(Trajectory, n, t->prev);
1384 n->divisions = t->divisions;
1386 n->status = PREDICTOR;
1394 /* Turn abstract timings into physically appropriate object trajectories. */
1396 projectile(jugglestruct *sp)
1399 const int yf = 0; /* Floor height */
1401 for (t = sp->head->next; t != sp->head; t = t->next) {
1402 if (t->status != PTHRATCH || t->action != THROW) {
1404 } else if (t->balllink == NULL) { /* Zero Throw */
1405 t->status = BPREDICTOR;
1406 } else if (t->balllink->handlink == NULL) { /* Incomplete */
1408 } else if(t->balllink == t->handlink) {
1409 /* '2' height - hold on to ball. Don't need to consider
1410 flourishes, 'hands' will do that automatically anyway */
1413 /* Zero spin to avoid wrist injuries */
1415 match_spins_on_catch(t, t);
1417 t->status = BPREDICTOR;
1420 if (t->posn == '_') { /* Bounce once */
1422 const int tb = t->start +
1423 find_bounce(sp, t->y, (double) yf, t->balllink->y,
1424 (double) (t->balllink->start - t->start),
1425 ObjectDefs[t->object->type].cor);
1427 if(tb < t->start) { /* bounce too hard */
1428 t->posn = '+'; /* Use regular throw */
1430 Trajectory *n; /* First (throw) trajectory. */
1431 double dt; /* Time span of a trajectory */
1432 double dy; /* Distance span of a follow-on trajectory.
1433 First trajectory uses t->dy */
1434 /* dx is constant across both trajectories */
1435 t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
1437 { /* ball follows parabola down */
1438 n = new_predictor(t, t->start, tb, t->angle);
1439 if(n == NULL) return False;
1440 dt = n->finish - n->start;
1441 /* Ball rate 4, no flight or matching club turns */
1442 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
1443 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1444 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1447 { /* ball follows parabola up */
1448 Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
1450 if(m == NULL) return False;
1451 dt = m->finish - m->start;
1452 /* Use previous ball rate, no flight club turns */
1453 m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
1454 t->balllink->angle - m->angle);
1455 match_spins_on_catch(t, m);
1456 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1457 makeParabola(m, t->balllink->x - t->dx * dt,
1458 t->dx, (double) yf, dy, sp->Gr);
1461 t->status = BPREDICTOR;
1464 } else if (t->posn == 'k') { /* Drop & Kick */
1465 Trajectory *n; /* First (drop) trajectory. */
1466 Trajectory *o; /* Second (rest) trajectory */
1467 Trajectory *m; /* Third (kick) trajectory */
1468 const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
1469 const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
1472 { /* Fall to ground */
1473 n = new_predictor(t, t->start, td, t->angle);
1474 if(n == NULL) return False;
1475 dt = n->finish - n->start;
1476 /* Ball spin rate 4, no flight club turns */
1477 n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
1478 t->balllink->angle - n->angle);
1479 t->dx = (t->balllink->x - t->x) / dt;
1480 t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
1481 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1484 { /* Rest on ground */
1485 o = new_predictor(t, n->finish, tk, end_spin(n));
1486 if(o == NULL) return False;
1488 makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
1493 m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
1494 if(m == NULL) return False;
1495 dt = m->finish - m->start;
1496 /* Match receiving hand, ball rate 4, one flight club turn */
1497 m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
1498 4, 1, t->balllink->angle - m->angle);
1499 match_spins_on_catch(t, m);
1500 dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
1501 makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
1504 t->status = BPREDICTOR;
1508 /* Regular flight, no bounce */
1509 { /* ball follows parabola */
1511 Trajectory *n = new_predictor(t, t->start,
1512 t->balllink->start, t->angle);
1513 if(n == NULL) return False;
1514 dt = t->balllink->start - t->start;
1516 n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
1517 t->balllink->angle - n->angle);
1518 match_spins_on_catch(t, n);
1519 t->dx = (t->balllink->x - t->x) / dt;
1520 t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
1521 makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
1524 t->status = BPREDICTOR;
1530 /* Turn abstract hand motions into cubic splines. */
1532 hands(jugglestruct *sp)
1534 Trajectory *t, *u, *v;
1536 for (t = sp->head->next; t != sp->head; t = t->next) {
1537 /* no throw => no velocity */
1538 if (t->status != BPREDICTOR) {
1543 if (u == NULL) { /* no next catch */
1547 if (v == NULL) { /* no next throw */
1551 /* double spline takes hand from throw, thru catch, to
1554 t->finish = u->start;
1555 t->status = PREDICTOR;
1557 u->finish = v->start;
1558 u->status = PREDICTOR;
1561 /* FIXME: These adjustments leave a small glitch when alternating
1562 balls and clubs. Just hope no-one notices. :-) */
1564 /* make sure empty hand spin matches the thrown object in case it
1567 t->spin = ((t->hand == LEFT)? -1 : 1 ) *
1568 fabs((u->angle - t->angle)/(u->start - t->start));
1570 u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
1571 fabs((v->angle - u->angle)/(v->start - u->start));
1573 (void) makeSplinePair(&t->xp, &u->xp,
1574 t->x, t->dx, t->start,
1576 v->x, v->dx, v->start);
1577 (void) makeSplinePair(&t->yp, &u->yp,
1578 t->y, t->dy, t->start,
1580 v->y, v->dy, v->start);
1582 t->status = PREDICTOR;
1586 /* Given target x, y find_elbow puts hand at target if possible,
1587 * otherwise makes hand point to the target */
1589 find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
1593 double x = p->x - s->x;
1594 double y = p->y - s->y;
1595 h2 = x*x + y*y + z*z;
1596 if (h2 > 4 * armlength * armlength) {
1597 t = armlength/sqrt(h2);
1600 h->x = 2 * t * x + s->x;
1601 h->y = 2 * t * y + s->y;
1603 r = sqrt((double)(x*x + z*z));
1604 t = sqrt(4 * armlength * armlength / h2 - 1);
1605 e->x = x*(1 + y*t/r)/2 + s->x;
1606 e->y = (y - r*t)/2 + s->y;
1613 /* NOTE: returned x, y adjusted for arm reach */
1615 reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
1617 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1619 find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
1620 *p = sp->arm[1][side][HAND] = h;
1621 sp->arm[1][side][ELBOW] = e;
1625 /* dumps a human-readable rendition of the current state of the juggle
1626 pipeline to stderr for debugging */
1628 dump(jugglestruct *sp)
1631 for (t = sp->head->next; t != sp->head; t = t->next) {
1632 switch (t->status) {
1634 (void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
1637 (void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
1638 t->pattern == NULL?"":t->pattern);
1641 if (t->action == CATCH)
1642 (void) fprintf(stderr, "%p A %c%cC\n",
1644 t->hand ? 'R' : 'L');
1646 (void) fprintf(stderr, "%p A %c%c%c%d\n",
1648 t->hand ? 'R' : 'L',
1649 (t->action == THROW)?'T':'N',
1653 (void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
1656 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1657 t->height, t->object == NULL?0:t->object->color,
1658 (void*)t->handlink, (void*)t->balllink);
1661 (void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
1664 (t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
1665 t->height, t->type, t->object == NULL?0:t->object->color,
1666 t->start, t->finish);
1669 (void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
1670 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1671 t->object == NULL?0:t->object->color,
1672 t->start, t->finish, t->yp.c);
1675 (void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
1676 (void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
1677 t->object == NULL?0:t->object->color,
1678 t->start, t->finish, t->yp.c);
1681 (void) fprintf(stderr, "%p: status %d not implemented\n",
1682 (void*)t, t->status);
1686 (void) fprintf(stderr, "---\n");
1690 static int get_num_balls(const char *j)
1696 for (p = j; *p; p++) {
1697 if (*p >= '0' && *p <='9') { /* digit */
1698 h = 10*h + (*p - '0');
1714 compare_num_balls(const void *p1, const void *p2)
1717 i = get_num_balls(((patternstruct*)p1)->pattern);
1718 j = get_num_balls(((patternstruct*)p2)->pattern);
1733 /**************************************************************************
1734 * Rendering Functions *
1736 **************************************************************************/
1739 show_arms(ModeInfo * mi, unsigned long color)
1741 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1744 XPoint a[XtNumber(sp->arm[0][0])];
1745 if(color == MI_BLACK_PIXEL(mi)) {
1750 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1751 ARMWIDTH, LineSolid, CapRound, JoinRound);
1752 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1753 for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
1754 /* Translate into device coords */
1755 for(i = 0; i < XtNumber(a); i++) {
1756 a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
1757 a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
1759 sp->arm[0][side][i] = sp->arm[1][side][i];
1761 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1762 a, XtNumber(a), CoordModeOrigin);
1767 show_figure(ModeInfo * mi, unsigned long color, Bool init)
1769 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1790 static const XPoint figure[] = {
1791 { 15, 70}, /* 0 Left Hip */
1792 { 0, 90}, /* 1 Waist */
1793 { SX, 130}, /* 2 Left Shoulder */
1794 {-SX, 130}, /* 3 Right Shoulder */
1795 {-15, 70}, /* 4 Right Hip */
1796 { 0, 130}, /* 5 Neck */
1797 { 0, 140}, /* 6 Chin */
1798 { SX, 0}, /* 7 Left Foot */
1799 {-SX, 0}, /* 8 Right Foot */
1800 {-17, 174}, /* 9 Head1 */
1801 { 17, 140}, /* 10 Head2 */
1803 XPoint a[XtNumber(figure)];
1805 /* Translate into device coords */
1806 for(i = 0; i < XtNumber(figure); i++) {
1807 a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
1808 a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
1811 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
1812 ARMWIDTH, LineSolid, CapRound, JoinRound);
1813 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1816 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
1817 p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
1818 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1819 p, i, CoordModeOrigin);
1822 p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
1823 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1824 p, i, CoordModeOrigin);
1827 p[i++] = a[5]; p[i++] = a[6];
1828 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1829 p, i, CoordModeOrigin);
1832 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1834 a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
1835 sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
1836 sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
1838 /* Initialise arms */
1840 for(i = 0; i < 2; i++){
1841 sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
1842 sp->arm[i][LEFT][ELBOW].x = figure[2].x;
1843 sp->arm[i][LEFT][ELBOW].y = figure[1].y;
1844 sp->arm[i][LEFT][HAND].x = figure[0].x;
1845 sp->arm[i][LEFT][HAND].y = figure[1].y;
1846 sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
1847 sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
1848 sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
1849 sp->arm[i][RIGHT][HAND].x = figure[4].x;
1850 sp->arm[i][RIGHT][HAND].y = figure[1].y;
1856 show_ball(ModeInfo *mi, unsigned long color, Trace *s)
1858 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1859 int offset = (int)(s->angle*64*180/M_PI);
1860 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
1861 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
1863 /* Avoid wrapping */
1864 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1866 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1867 if (s->divisions == 0 || color == MI_BLACK_PIXEL(mi)) {
1868 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1869 x - BALLRADIUS, y - BALLRADIUS,
1870 2*BALLRADIUS, 2*BALLRADIUS,
1872 } else if (s->divisions == 4) { /* 90 degree divisions */
1873 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1874 x - BALLRADIUS, y - BALLRADIUS,
1875 2*BALLRADIUS, 2*BALLRADIUS,
1876 offset % 23040, 5760);
1877 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1878 x - BALLRADIUS, y - BALLRADIUS,
1879 2*BALLRADIUS, 2*BALLRADIUS,
1880 (offset + 11520) % 23040, 5760);
1882 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1883 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1884 x - BALLRADIUS, y - BALLRADIUS,
1885 2*BALLRADIUS, 2*BALLRADIUS,
1886 (offset + 5760) % 23040, 5760);
1887 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1888 x - BALLRADIUS, y - BALLRADIUS,
1889 2*BALLRADIUS, 2*BALLRADIUS,
1890 (offset + 17280) % 23040, 5760);
1891 } else if (s->divisions == 2) { /* 180 degree divisions */
1892 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1893 x - BALLRADIUS, y - BALLRADIUS,
1894 2*BALLRADIUS, 2*BALLRADIUS,
1895 offset % 23040, 11520);
1897 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1898 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1899 x - BALLRADIUS, y - BALLRADIUS,
1900 2*BALLRADIUS, 2*BALLRADIUS,
1901 (offset + 11520) % 23040, 11520);
1903 (void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
1904 MI_SCREEN(mi), s->divisions);
1909 show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
1911 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
1913 const double sa = sin(s->angle);
1914 const double ca = cos(s->angle);
1935 static const XPoint club[] = {
1948 {-24, 2}, /* 0 close boundary */
1950 XPoint a[XtNumber(club)];
1952 /* Avoid wrapping */
1953 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
1955 /* Translate and fake perspective */
1956 for(i = 0; i < XtNumber(club); i++) {
1957 a[i].x = (short)(MI_WIDTH(mi)/2 +
1958 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
1959 club[i].y*sqrt(sp->scale)*ca);
1960 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
1961 club[i].y*sa*sqrt(sp->scale));
1964 if(color != MI_BLACK_PIXEL(mi)) {
1965 /* Outline in black */
1966 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
1967 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
1968 LineSolid, CapRound, JoinRound);
1969 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1970 a, XtNumber(a), CoordModeOrigin);
1973 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
1975 /* Don't be tempted to optimize erase by drawing all the black in
1976 one X operation. It must use the same ops as the colours to
1977 guarantee a clean erase. */
1979 i = 0; /* Colored stripes */
1980 p[i++] = a[1]; p[i++] = a[2];
1981 p[i++] = a[9]; p[i++] = a[10];
1982 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1983 p, i, Convex, CoordModeOrigin);
1985 p[i++] = a[3]; p[i++] = a[4];
1986 p[i++] = a[7]; p[i++] = a[8];
1987 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1988 p, i, Convex, CoordModeOrigin);
1990 if(color != MI_BLACK_PIXEL(mi)) {
1991 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
1994 i = 0; /* White center band */
1995 p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
1996 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
1997 p, i, Convex, CoordModeOrigin);
1999 i = 0; /* White handle */
2000 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
2001 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2002 p, i, Convex, CoordModeOrigin);
2004 i = 0; /* White tip */
2005 p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
2006 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2007 p, i, Convex, CoordModeOrigin);
2012 show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
2014 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2016 const double sa = sin(s->angle);
2017 const double ca = cos(s->angle);
2037 static const XPoint club[] = {
2048 {-24, 2}, /* 0 close boundary */
2050 XPoint a[XtNumber(club)];
2052 /* Avoid wrapping */
2053 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2055 /* Translate and fake perspective */
2056 for(i = 0; i < XtNumber(club); i++) {
2057 a[i].x = (short)(MI_WIDTH(mi)/2 +
2058 (s->x + club[i].x*PERSPEC*sa)*sp->scale -
2059 club[i].y*sqrt(sp->scale)*ca);
2060 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
2061 club[i].y*sa*sqrt(sp->scale));
2064 if(color != MI_BLACK_PIXEL(mi)) {
2065 /* Outline in black */
2066 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2067 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2068 LineSolid, CapRound, JoinRound);
2069 XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2070 a, XtNumber(a), CoordModeOrigin);
2073 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2075 /* Don't be tempted to optimize erase by drawing all the black in
2076 one X operation. It must use the same ops as the colours to
2077 guarantee a clean erase. */
2079 i = 0; /* Coloured center band */
2080 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
2081 p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
2082 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2083 p, i, Convex, CoordModeOrigin);
2085 if(color != MI_BLACK_PIXEL(mi)) {
2086 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2089 i = 0; /* White handle */
2090 p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
2091 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2092 p, i, Convex, CoordModeOrigin);
2094 i = 0; /* White tip */
2095 p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
2096 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2097 p, i, Convex, CoordModeOrigin);
2102 show_torch(ModeInfo *mi, unsigned long color, Trace *s)
2104 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2105 XPoint head, tail, last;
2106 DXPoint dhead, dlast;
2107 const double sa = sin(s->angle);
2108 const double ca = cos(s->angle);
2110 const double TailLen = -24;
2111 const double HeadLen = 16;
2112 const short Width = (short)(5 * sqrt(sp->scale));
2124 dhead.x = s->x + HeadLen * PERSPEC * sa;
2125 dhead.y = s->y - HeadLen * ca;
2127 if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
2129 } else { /* Store 'last' so we can use it later when s->prev has
2131 if(s->prev != s->next) {
2132 dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
2133 dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
2140 /* Avoid wrapping (after last is stored) */
2141 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2143 head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
2144 head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
2146 last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
2147 last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
2149 tail.x = (short)(MI_WIDTH(mi)/2 +
2150 (s->x + TailLen * PERSPEC * sa)*sp->scale );
2151 tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
2153 if(color != MI_BLACK_PIXEL(mi)) {
2154 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2155 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2156 Width, LineSolid, CapRound, JoinRound);
2157 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2158 head.x, head.y, tail.x, tail.y);
2160 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2161 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2162 Width * 2, LineSolid, CapRound, JoinRound);
2164 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2165 head.x, head.y, last.x, last.y);
2170 show_knife(ModeInfo *mi, unsigned long color, Trace *s)
2172 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2174 const double sa = sin(s->angle);
2175 const double ca = cos(s->angle);
2186 static const XPoint knife[] = {
2194 XPoint a[XtNumber(knife)], p[5];
2196 /* Avoid wrapping */
2197 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2199 /* Translate and fake perspective */
2200 for(i = 0; i < XtNumber(knife); i++) {
2201 a[i].x = (short)(MI_WIDTH(mi)/2 +
2202 (s->x + knife[i].x*PERSPEC*sa)*sp->scale -
2203 knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
2204 a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
2205 knife[i].y*sa*sqrt(sp->scale));
2209 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2210 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
2211 LineSolid, CapRound, JoinRound);
2212 XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2213 a[0].x, a[0].y, a[4].x, a[4].y);
2216 if(color != MI_BLACK_PIXEL(mi)) {
2217 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2220 p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
2221 XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2222 p, i, Convex, CoordModeOrigin);
2226 show_ring(ModeInfo *mi, unsigned long color, Trace *s)
2228 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2229 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2230 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2231 double radius = 15 * sp->scale;
2232 short thickness = (short)(8 * sqrt(sp->scale));
2234 /* Avoid wrapping */
2235 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2237 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2238 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
2239 thickness, LineSolid, CapRound, JoinRound);
2241 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2242 (short)(x - radius*PERSPEC), (short)(y - radius),
2243 (short)(2*radius*PERSPEC), (short)(2*radius),
2249 show_bball(ModeInfo *mi, unsigned long color, Trace *s)
2251 jugglestruct *sp = &juggles[MI_SCREEN(mi)];
2252 short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
2253 short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
2254 double radius = 12 * sp->scale;
2255 int offset = (int)(s->angle*64*180/M_PI);
2256 int holesize = (int)(3.0*sqrt(sp->scale));
2258 /* Avoid wrapping */
2259 if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
2261 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2262 XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2263 (short)(x - radius), (short)(y - radius),
2264 (short)(2*radius), (short)(2*radius),
2266 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
2267 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
2268 LineSolid, CapRound, JoinRound);
2269 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2270 (short)(x - radius), (short)(y - radius),
2271 (short)(2*radius), (short)(2*radius),
2274 /* Draw finger holes */
2275 XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
2276 LineSolid, CapRound, JoinRound);
2278 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2279 (short)(x - radius*0.5), (short)(y - radius*0.5),
2280 (short)(2*radius*0.5), (short)(2*radius*0.5),
2281 (offset + 960) % 23040, 0);
2282 XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2283 (short)(x - radius*0.7), (short)(y - radius*0.7),
2284 (short)(2*radius*0.7), (short)(2*radius*0.7),
2285 (offset + 1920) % 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),
2292 /**************************************************************************
2293 * Public Functions *
2295 **************************************************************************/
2299 release_juggle (ModeInfo * mi)
2301 if (juggles != NULL) {
2304 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
2305 free_juggle(&juggles[screen]);
2307 juggles = (jugglestruct *) NULL;
2311 /* FIXME: refill_juggle currently just appends new throws to the
2312 * programme. This is fine if the programme is empty, but if there
2313 * are still some trajectories left then it really should take these
2317 refill_juggle(ModeInfo * mi)
2319 jugglestruct *sp = NULL;
2322 if (juggles == NULL)
2324 sp = &juggles[MI_SCREEN(mi)];
2326 /* generate pattern */
2327 if (pattern == NULL) {
2330 #define MAXREPEAT 300
2331 #define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
2332 #define POSITION_BIAS 20 /* larger makes hand movements less likely */
2335 while (count < MI_CYCLES(mi)) {
2336 char buf[MAXPAT * 3 + 3], *b = buf;
2338 int l = NRAND(MAXPAT) + 1;
2339 int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
2341 { /* vary number of balls */
2342 int new_balls = sp->num_balls;
2345 if (new_balls == 2) /* Do not juggle 2 that often */
2346 change = NRAND(2 + CHANGE_BIAS / 4);
2348 change = NRAND(2 + CHANGE_BIAS);
2359 if (new_balls < sp->patternindex.minballs) {
2362 if (new_balls > sp->patternindex.maxballs) {
2365 if (new_balls < sp->num_balls) {
2366 if (!program(mi, "[*]", NULL, 1)) /* lose ball */
2369 sp->num_balls = new_balls;
2373 if (NRAND(2) && sp->patternindex.index[sp->num_balls].number) {
2374 /* Pick from PortFolio */
2375 int p = sp->patternindex.index[sp->num_balls].start +
2376 NRAND(sp->patternindex.index[sp->num_balls].number);
2377 if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
2380 /* Invent a new pattern */
2382 for(i = 0; i < l; i++){
2384 do { /* Triangular Distribution => high values more likely */
2385 m = NRAND(sp->num_balls + 1);
2386 n = NRAND(sp->num_balls + 1);
2388 if (n == sp->num_balls) {
2391 switch(NRAND(5 + POSITION_BIAS)){
2392 case 0: /* Outside throw */
2394 case 1: /* Cross throw */
2396 case 2: /* Cross catch */
2398 case 3: /* Cross throw and catch */
2400 case 4: /* Bounce */
2403 break; /* Inside throw (default) */
2412 if (!program(mi, buf, NULL, t))
2417 } else { /* pattern supplied in height or 'a' notation */
2418 if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
2435 if (!projectile(sp)) {
2442 if(MI_IS_DEBUG(mi)) dump(sp);
2447 change_juggle(ModeInfo * mi)
2449 jugglestruct *sp = NULL;
2452 if (juggles == NULL)
2454 sp = &juggles[MI_SCREEN(mi)];
2456 /* Strip pending trajectories */
2457 for (t = sp->head->next; t != sp->head; t = t->next) {
2458 if(t->start > sp->time || t->finish < sp->time) {
2461 trajectory_destroy(n);
2465 /* Pick the current object theme */
2466 sp->objtypes = choose_object();
2470 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
2471 don't all those special effects. */
2472 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2474 show_figure(mi, MI_WHITE_PIXEL(mi), True);
2479 init_juggle (ModeInfo * mi)
2481 jugglestruct *sp = 0;
2484 if (juggles == NULL) { /* First-time initialisation */
2486 /* allocate jugglestruct */
2488 (jugglestruct *)calloc(MI_NUM_SCREENS(mi),
2489 sizeof (jugglestruct))) == NULL) {
2495 sp = &juggles[MI_SCREEN(mi)];
2497 if (only && *only && strcmp(only, " ")) {
2498 balls = clubs = torches = knives = rings = bballs = False;
2499 if (!strcasecmp (only, "balls")) balls = True;
2500 else if (!strcasecmp (only, "clubs")) clubs = True;
2501 else if (!strcasecmp (only, "torches")) torches = True;
2502 else if (!strcasecmp (only, "knives")) knives = True;
2503 else if (!strcasecmp (only, "rings")) rings = True;
2504 else if (!strcasecmp (only, "bballs")) bballs = True;
2506 (void) fprintf (stderr,
2507 "Juggle: -only must be one of: balls, clubs, torches, knives,\n"
2508 "\t rings, or bballs (not \"%s\")\n", only);
2509 #ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
2515 if (sp->head == 0) { /* first time initializing this juggler */
2517 sp->count = ABS(MI_COUNT(mi));
2521 /* record start time */
2522 sp->begintime = time(NULL);
2523 if(sp->patternindex.maxballs > 0) {
2524 sp->num_balls = sp->patternindex.minballs +
2525 NRAND(sp->patternindex.maxballs - sp->patternindex.minballs);
2528 show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
2529 information about the juggler's
2532 /* "7" should be about three times the height of the juggler's
2534 sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
2535 7 * THROW_CATCH_INTERVAL);
2537 if(!balls && !clubs && !torches && !knives && !rings && !bballs)
2538 balls = True; /* Have to juggle something! */
2540 /* create circular trajectory list */
2541 ADD_ELEMENT(Trajectory, sp->head, sp->head);
2542 if(sp->head == NULL){
2547 /* create circular object list */
2548 ADD_ELEMENT(Object, sp->objects, sp->objects);
2549 if(sp->objects == NULL){
2554 /* create circular wander list */
2555 ADD_ELEMENT(Wander, sp->wander, sp->wander);
2556 if(sp->wander == NULL){
2560 (void)wander(sp, 0); /* Initialize wander */
2562 sp->pattern = strdup(""); /* Initialise saved pattern with
2566 sp = &juggles[MI_SCREEN(mi)];
2570 !strcasecmp (pattern, ".") ||
2571 !strcasecmp (pattern, "random")))
2574 if (pattern == NULL && sp->patternindex.maxballs == 0) {
2575 /* pattern list needs indexing */
2576 int nelements = XtNumber(portfolio);
2579 /* sort according to number of balls */
2580 qsort((void*)portfolio, nelements,
2581 sizeof(portfolio[1]), compare_num_balls);
2583 /* last pattern has most balls */
2584 sp->patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
2585 /* run through sorted list, indexing start of each group
2586 and number in group */
2587 sp->patternindex.maxballs = 1;
2588 for (i = 0; i < nelements; i++) {
2589 int b = get_num_balls(portfolio[i].pattern);
2590 if (b > sp->patternindex.maxballs) {
2591 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2592 if(numpat == 0) sp->patternindex.minballs = b;
2593 sp->patternindex.maxballs = b;
2595 sp->patternindex.index[sp->patternindex.maxballs].start = i;
2600 sp->patternindex.index[sp->patternindex.maxballs].number = numpat;
2603 /* Set up programme */
2606 /* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
2607 only be resizing and then we won't all those special effects. */
2608 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2610 /* Only put things here that won't interrupt the programme during
2613 /* Use MIN so that users can resize in interesting ways, eg
2614 narrow windows for tall patterns, etc */
2615 sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
2617 if(describe && !sp->mode_font) { /* Check to see if there's room to describe patterns. */
2618 char *font = get_string_resource (MI_DISPLAY(mi), "font", "Font");
2619 sp->mode_font = XLoadQueryFont(MI_DISPLAY(mi), font);
2624 reshape_juggle (ModeInfo * mi, int width, int height)
2630 draw_juggle (ModeInfo * mi)
2632 Trajectory *traj = NULL;
2634 unsigned long future = 0;
2635 jugglestruct *sp = NULL;
2636 char *pattern = NULL;
2639 if (juggles == NULL)
2641 sp = &juggles[MI_SCREEN(mi)];
2643 MI_IS_DRAWN(mi) = True;
2646 /* Don't worry about flicker, trust Quartz's double-buffering.
2647 This is a fast fix for the pixel-turds I can't track down...
2649 XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
2655 (void)gettimeofday(&tv, NULL);
2656 sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
2658 sp->time += MI_DELAY(mi) / 1000;
2661 /* First pass: Move arms and strip out expired elements */
2662 for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
2663 if (traj->status != PREDICTOR) {
2664 /* Skip any elements that need further processing */
2665 /* We could remove them, but there shoudn't be many and they
2666 would be needed if we ever got the pattern refiller
2670 if (traj->start > future) { /* Lookahead to the end of the show */
2671 future = traj->start;
2673 if (sp->time < traj->start) { /* early */
2675 } else if (sp->time < traj->finish) { /* working */
2677 /* Look for pattern name */
2678 if(traj->pattern != NULL) {
2679 pattern=traj->pattern;
2682 if (traj->type == Empty || traj->type == Full) {
2683 /* Only interested in hands on this pass */
2684 double angle = traj->angle + traj->spin * (sp->time - traj->start);
2685 double xd = 0, yd = 0;
2688 /* Find the catching offset */
2689 if(traj->object != NULL) {
2690 if(ObjectDefs[traj->object->type].handle > 0) {
2691 /* Handles Need to be oriented */
2692 xd = ObjectDefs[traj->object->type].handle *
2693 PERSPEC * sin(angle);
2694 yd = ObjectDefs[traj->object->type].handle *
2697 /* Balls are always caught at the bottom */
2702 p.x = (CUBIC(traj->xp, sp->time) - xd);
2703 p.y = (CUBIC(traj->yp, sp->time) + yd);
2704 reach_arm(mi, traj->hand, &p);
2706 /* Store updated hand position */
2710 if (traj->type == Ball || traj->type == Full) {
2711 /* Only interested in objects on this pass */
2715 if(traj->type == Full) {
2716 /* Adjusted these in the first pass */
2720 x = CUBIC(traj->xp, sp->time);
2721 y = CUBIC(traj->yp, sp->time);
2724 ADD_ELEMENT(Trace, s, traj->object->trace->prev);
2727 s->angle = traj->angle + traj->spin * (sp->time - traj->start);
2728 s->divisions = traj->divisions;
2729 traj->object->tracelen++;
2730 traj->object->active = True;
2732 } else { /* expired */
2733 Trajectory *n = traj;
2735 trajectory_destroy(n);
2739 /* Erase end of trails */
2740 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2742 for (s = o->trace->next;
2743 o->trace->next != o->trace &&
2744 (o->count == 0 || o->tracelen > o->tail);
2745 s = o->trace->next) {
2746 ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
2749 if(o->count <= 0 && o->tracelen <= 0) {
2750 /* Object no longer in use and trail gone */
2755 if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
2759 show_arms(mi, MI_BLACK_PIXEL(mi));
2760 cx = wander(sp, sp->time);
2761 /* Reduce flicker by only permitting movements of more than a pixel */
2762 if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
2763 show_figure(mi, MI_BLACK_PIXEL(mi), False);
2767 show_figure(mi, MI_WHITE_PIXEL(mi), False);
2769 show_arms(mi, MI_WHITE_PIXEL(mi));
2772 for (o = sp->objects->next; o != sp->objects; o = o->next) {
2774 ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
2780 /* Save pattern name so we can erase it when it changes */
2781 if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
2782 /* Erase old name */
2783 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
2785 XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2786 0, 20, sp->pattern, strlen(sp->pattern));
2788 XFillRectangle(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2789 0, 0, MI_WIDTH(mi), 25);
2792 sp->pattern = strdup(pattern);
2794 if (MI_IS_VERBOSE(mi)) {
2795 (void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
2796 MI_SCREEN(mi), sp->pattern);
2799 if(sp->mode_font != None &&
2800 XTextWidth(sp->mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
2801 /* Redraw once a cycle, in case it's obscured or it changed */
2802 XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
2803 XDrawImageString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
2804 0, 20, sp->pattern, strlen(sp->pattern));
2808 if((int)(sp->time/10) % 1000 == 0)
2809 (void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
2812 if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
2814 } else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
2820 XSCREENSAVER_MODULE ("Juggle", juggle)
2822 #endif /* MODE_juggle */