2816 lines
76 KiB
C
2816 lines
76 KiB
C
/* -*- Mode: C; tab-width: 4 -*- */
|
|
/* juggle */
|
|
|
|
#if !defined( lint ) && !defined( SABER )
|
|
static const char sccsid[] = "@(#)juggle.c 5.10 2003/09/02 xlockmore";
|
|
|
|
#endif
|
|
|
|
/*-
|
|
* Copyright (c) 1996 by Tim Auckland <tda10.geo@yahoo.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software and its
|
|
* documentation for any purpose and without fee is hereby granted,
|
|
* provided that the above copyright notice appear in all copies and that
|
|
* both that copyright notice and this permission notice appear in
|
|
* supporting documentation.
|
|
*
|
|
* This file is provided AS IS with no warranties of any kind. The author
|
|
* shall have no liability with respect to the infringement of copyrights,
|
|
* trade secrets or any patents by this file or any part thereof. In no
|
|
* event will the author be liable for any lost revenue or profits or
|
|
* other special, indirect and consequential damages.
|
|
*
|
|
* Revision History
|
|
* 13-Dec-2004: [TDA] Use -cycles and -count in a rational manner.
|
|
* Add -rings, -bballs. Add -describe. Finally made
|
|
* live pattern updates possible. Add refill_juggle(),
|
|
* change_juggle() and reshape_juggle(). Make
|
|
* init_juggle() non-destructive. Reorder erase/draw
|
|
* operations. Update xscreensaver xml and manpage.
|
|
* 15-Nov-2004: [TDA] Fix all memory leaks.
|
|
* 12-Nov-2004: [TDA] Add -torches and another new trail
|
|
* implementation, so that different objects can have
|
|
* different length trails.
|
|
* 11-Nov-2004: [TDA] Clap when all the balls are in the air.
|
|
* 10-Nov-2004: [TDA] Display pattern name converted to hight
|
|
* notation.
|
|
* 31-Oct-2004: [TDA] Add -clubs and new trail implementation.
|
|
* 02-Sep-2003: Non-real time to see what is happening without a
|
|
* strobe effect for slow machines.
|
|
* 01-Nov-2000: Allocation checks
|
|
* 1996: Written
|
|
*/
|
|
|
|
/*-
|
|
* TODO
|
|
* Implement the anonymously promised -uni option.
|
|
*/
|
|
|
|
|
|
/*
|
|
* Notes on Adam Chalcraft Juggling Notation (used by permission)
|
|
* a-> Adam's notation s-> Site swap (Cambridge) notation
|
|
*
|
|
* To define a map from a-notation to s-notation ("site-swap"), both
|
|
* of which look like doubly infinite sequences of natural numbers. In
|
|
* s-notation, there is a restriction on what is allowed, namely for
|
|
* the sequence s_n, the associated function f(n)=n+s_n must be a
|
|
* bijection. In a-notation, there is no restriction.
|
|
*
|
|
* To go from a-notation to s-notation, you start by mapping each a_n
|
|
* to a permutation of N, the natural numbers.
|
|
*
|
|
* 0 -> the identity
|
|
* 1 -> (10) [i.e. f(1)=0, f(0)=1]
|
|
* 2 -> (210) [i.e. f(2)=1, f(1)=0, f(0)=2]
|
|
* 3 -> (3210) [i.e. f(3)=2, f(2)=1, f(1)=0, f(0)=3]
|
|
* etc.
|
|
*
|
|
* Then for each n, you look at how long 0 takes to get back to 0
|
|
* again and you call this t_n. If a_n=0, for example, then since the
|
|
* identity leaves 0 alone, it gets back to 0 in 1 step, so t_n=1. If
|
|
* a_n=1, then f(0)=1. Now any further a_n=0 leave 1 alone, but the
|
|
* next a_n>0 sends 1 back to 0. Hence t_n is 2 + the number of 0's
|
|
* following the 1. Finally, set s_n = t_n - 1.
|
|
*
|
|
* To give some examples, it helps to have a notation for cyclic
|
|
* sequences. By (123), for example, I mean ...123123123123... . Now
|
|
* under the a-notation -> s-notation mapping we have some familiar
|
|
* examples:
|
|
*
|
|
* (0)->(0), (1)->(1), (2)->(2) etc.
|
|
* (21)->(31), (31)->(51), (41)->(71) etc.
|
|
* (10)->(20), (20)->(40), (30)->(60) etc.
|
|
* (331)->(441), (312)->(612), (303)->(504), (321)->(531)
|
|
* (43)->(53), (434)->(534), (433)->(633)
|
|
* (552)->(672)
|
|
*
|
|
* In general, the number of balls is the *average* of the s-notation,
|
|
* and the *maximum* of the a-notation. Another theorem is that the
|
|
* minimum values in the a-notation and the s-notation and equal, and
|
|
* preserved in the same positions.
|
|
*
|
|
* The usefulness of a-notation is the fact that there are no
|
|
* restrictions on what is allowed. This makes random juggle
|
|
* generation much easier. It also makes enumeration very
|
|
* easy. Another handy feature is computing changes. Suppose you can
|
|
* do (5) and want a neat change up to (771) in s-notation [Mike Day
|
|
* actually needed this example!]. Write them both in a-notation,
|
|
* which gives (5) and (551). Now concatenate them (in general, there
|
|
* may be more than one way to do this, but not in this example), to
|
|
* get
|
|
*
|
|
* ...55555555551551551551551...
|
|
*
|
|
* Now convert back to s-notation, to get
|
|
*
|
|
* ...55555566771771771771771...
|
|
*
|
|
* So the answer is to do two 6 throws and then go straight into
|
|
* (771). Coming back down of course,
|
|
*
|
|
* ...5515515515515515555555555...
|
|
*
|
|
* converts to
|
|
*
|
|
* ...7717717717716615555555555...
|
|
*
|
|
* so the answer is to do a single 661 and then drop straight down to
|
|
* (5).
|
|
*
|
|
* [The number of balls in the generated pattern occasionally changes.
|
|
* In order to decrease the number of balls I had to introduce a new
|
|
* symbol into the Adam notation, [*] which means 'lose the current
|
|
* ball'.]
|
|
*/
|
|
|
|
/* This code uses so many linked lists it's worth having a built-in
|
|
* leak-checker */
|
|
#undef MEMTEST
|
|
|
|
#ifdef STANDALONE
|
|
#define MODE_juggle
|
|
#define PROGCLASS "Juggle"
|
|
#define HACK_INIT init_juggle
|
|
#define HACK_DRAW draw_juggle
|
|
#define HACK_RESHAPE reshape_juggle
|
|
#define _no_HACK_FREE release_juggle
|
|
#define juggle_opts xlockmore_opts
|
|
#define DEFAULTS "*delay: 10000 \n" \
|
|
"*count: 200 \n" \
|
|
"*cycles: 1000 \n" \
|
|
"*ncolors: 32 \n" \
|
|
"*font: -*-times-bold-r-normal-*-180-*\n"
|
|
#undef SMOOTH_COLORS
|
|
#include "xlockmore.h" /* in xscreensaver distribution */
|
|
#define MI_DELAY(MI) ((MI)->pause)
|
|
# ifndef MI_DEPTH
|
|
# define MI_DEPTH MI_WIN_DEPTH
|
|
# endif
|
|
#else /* STANDALONE */
|
|
#include "xlock.h" /* in xlockmore distribution */
|
|
#endif /* STANDALONE */
|
|
|
|
#ifdef MODE_juggle
|
|
|
|
#if 0
|
|
#define XClearWindow(d, w) \
|
|
{ \
|
|
XSetForeground(d, MI_GC(mi), MI_PIXEL(mi, 3)); \
|
|
XFillRectangle(d, w, MI_GC(mi), \
|
|
0, 0, (unsigned int) MI_WIDTH(mi), (unsigned int) MI_HEIGHT(mi)); \
|
|
}
|
|
#endif
|
|
|
|
#define DEF_PATTERN "." /* All patterns */
|
|
#define DEF_TAIL "1" /* No trace */
|
|
#ifdef UNI
|
|
/* Maybe a ROLA BOLA would be at a better angle for viewing */
|
|
#define DEF_UNI "False" /* No unicycle */ /* Not implemented yet */
|
|
#endif
|
|
#define DEF_REAL "True"
|
|
#define DEF_DESCRIBE "True"
|
|
|
|
#define DEF_BALLS "True" /* Use Balls */
|
|
#define DEF_CLUBS "True" /* Use Clubs */
|
|
#define DEF_TORCHES "True" /* Use Torches */
|
|
#define DEF_KNIVES "True" /* Use Knives */
|
|
#define DEF_RINGS "True" /* Use Rings */
|
|
#define DEF_BBALLS "True" /* Use Bowling Balls */
|
|
|
|
#ifndef XtNumber
|
|
#define XtNumber(arr) ((unsigned int) (sizeof(arr) / sizeof(arr[0])))
|
|
#endif
|
|
|
|
static char *pattern;
|
|
static int tail;
|
|
#ifdef UNI
|
|
static Bool uni;
|
|
#endif
|
|
static Bool real;
|
|
static Bool describe;
|
|
static Bool balls;
|
|
static Bool clubs;
|
|
static Bool torches;
|
|
static Bool knives;
|
|
static Bool rings;
|
|
static Bool bballs;
|
|
static char *only;
|
|
|
|
static XrmOptionDescRec opts[] =
|
|
{
|
|
{"-pattern", ".juggle.pattern", XrmoptionSepArg, NULL },
|
|
{"-tail", ".juggle.tail", XrmoptionSepArg, NULL },
|
|
#ifdef UNI
|
|
{"-uni", ".juggle.uni", XrmoptionNoArg, "on" },
|
|
{"+uni", ".juggle.uni", XrmoptionNoArg, "off" },
|
|
#endif
|
|
{"-real", ".juggle.real", XrmoptionNoArg, "on" },
|
|
{"+real", ".juggle.real", XrmoptionNoArg, "off" },
|
|
{"-describe", ".juggle.describe", XrmoptionNoArg, "on" },
|
|
{"+describe", ".juggle.describe", XrmoptionNoArg, "off" },
|
|
{"-balls", ".juggle.balls", XrmoptionNoArg, "on" },
|
|
{"+balls", ".juggle.balls", XrmoptionNoArg, "off" },
|
|
{"-clubs", ".juggle.clubs", XrmoptionNoArg, "on" },
|
|
{"+clubs", ".juggle.clubs", XrmoptionNoArg, "off" },
|
|
{"-torches", ".juggle.torches", XrmoptionNoArg, "on" },
|
|
{"+torches", ".juggle.torches", XrmoptionNoArg, "off" },
|
|
{"-knives", ".juggle.knives", XrmoptionNoArg, "on" },
|
|
{"+knives", ".juggle.knives", XrmoptionNoArg, "off" },
|
|
{"-rings", ".juggle.rings", XrmoptionNoArg, "on" },
|
|
{"+rings", ".juggle.rings", XrmoptionNoArg, "off" },
|
|
{"-bballs", ".juggle.bballs", XrmoptionNoArg, "on" },
|
|
{"+bballs", ".juggle.bballs", XrmoptionNoArg, "off" },
|
|
{"-only", ".juggle.only", XrmoptionSepArg, NULL },
|
|
};
|
|
static argtype vars[] =
|
|
{
|
|
{ &pattern, "pattern", "Pattern", DEF_PATTERN, t_String },
|
|
{ &tail, "tail", "Tail", DEF_TAIL, t_Int },
|
|
#ifdef UNI
|
|
{ &uni, "uni", "Uni", DEF_UNI, t_Bool },
|
|
#endif
|
|
{ &real, "real", "Real", DEF_REAL, t_Bool },
|
|
{ &describe, "describe", "Describe", DEF_DESCRIBE, t_Bool },
|
|
{ &balls, "balls", "Clubs", DEF_BALLS, t_Bool },
|
|
{ &clubs, "clubs", "Clubs", DEF_CLUBS, t_Bool },
|
|
{ &torches, "torches", "Torches", DEF_TORCHES, t_Bool },
|
|
{ &knives, "knives", "Knives", DEF_KNIVES, t_Bool },
|
|
{ &rings, "rings", "Rings", DEF_RINGS, t_Bool },
|
|
{ &bballs, "bballs", "BBalls", DEF_BBALLS, t_Bool },
|
|
{ &only, "only", "BBalls", " ", t_String },
|
|
};
|
|
static OptionStruct desc[] =
|
|
{
|
|
{ "-pattern string", "Cambridge Juggling Pattern" },
|
|
{ "-tail num", "Trace Juggling Patterns" },
|
|
#ifdef UNI
|
|
{ "-/+uni", "Unicycle" },
|
|
#endif
|
|
{ "-/+real", "Real-time" },
|
|
{ "-/+describe", "turn on/off pattern descriptions." },
|
|
{ "-/+balls", "turn on/off Balls." },
|
|
{ "-/+clubs", "turn on/off Clubs." },
|
|
{ "-/+torches", "turn on/off Flaming Torches." },
|
|
{ "-/+knives", "turn on/off Knives." },
|
|
{ "-/+rings", "turn on/off Rings." },
|
|
{ "-/+bballs", "turn on/off Bowling Balls." },
|
|
{ "-only", "Turn off all objects but the named one." },
|
|
};
|
|
|
|
ModeSpecOpt juggle_opts =
|
|
{ XtNumber(opts), opts, XtNumber(vars), vars, desc };
|
|
|
|
#ifdef USE_MODULES
|
|
ModStruct juggle_description = {
|
|
"juggle", "init_juggle", "draw_juggle", "release_juggle",
|
|
"draw_juggle", "change_juggle", (char *) NULL, &juggle_opts,
|
|
10000, 200, 1000, 1, 64, 1.0, "",
|
|
"Shows a Juggler, juggling", 0, NULL
|
|
};
|
|
|
|
#endif
|
|
|
|
#ifdef USE_XVMSUTILS
|
|
#include <X11/unix_time.h>
|
|
#endif
|
|
#if HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#else
|
|
#if HAVE_SYS_SELECT_H
|
|
#include <sys/select.h>
|
|
#endif
|
|
#endif
|
|
|
|
/* Note: All "lengths" are scaled by sp->scale = MI_HEIGHT/480. All
|
|
"thicknesses" are scaled by sqrt(sp->scale) so that they are
|
|
proportionally thicker for smaller windows. Objects spinning out
|
|
of the plane (such as clubs) fake perspective by compressing their
|
|
horizontal coordinates by PERSPEC */
|
|
|
|
/* Figure */
|
|
#define ARMLENGTH 50
|
|
#define ARMWIDTH ((int) (8.0 * sqrt(sp->scale)))
|
|
#define POSE 10
|
|
#define BALLRADIUS ARMWIDTH
|
|
|
|
#define PERSPEC 0.4
|
|
|
|
/* macros */
|
|
#define GRAVITY(h, t) 4*(double)(h)/((t)*(t))
|
|
|
|
/* Timing based on count. Units are milliseconds. Juggles per second
|
|
is: 2000 / THROW_CATCH_INTERVAL + CATCH_THROW_INTERVAL */
|
|
|
|
#define THROW_CATCH_INTERVAL (sp->count)
|
|
#define THROW_NULL_INTERVAL (sp->count * 0.5)
|
|
#define CATCH_THROW_INTERVAL (sp->count * 0.2)
|
|
|
|
/********************************************************************
|
|
* Trace Definitions *
|
|
* *
|
|
* These record rendering data so that a drawn object can be erased *
|
|
* later. Each object has its own Trace list. *
|
|
* *
|
|
********************************************************************/
|
|
|
|
typedef struct {double x, y; } DXPoint;
|
|
typedef struct trace *TracePtr;
|
|
typedef struct trace {
|
|
TracePtr next, prev;
|
|
double x, y;
|
|
double angle;
|
|
int divisions;
|
|
DXPoint dlast;
|
|
#ifdef MEMTEST
|
|
char pad[1024];
|
|
#endif
|
|
} Trace;
|
|
|
|
/*******************************************************************
|
|
* Object Definitions *
|
|
* *
|
|
* These describe the various types of Object that can be juggled *
|
|
* *
|
|
*******************************************************************/
|
|
typedef void (DrawProc)(ModeInfo*, unsigned long, Trace *);
|
|
|
|
static DrawProc show_ball, show_europeanclub, show_torch, show_knife;
|
|
static DrawProc show_ring, show_bball;
|
|
|
|
typedef enum {BALL, CLUB, TORCH, KNIFE, RING, BBALLS,
|
|
NUM_OBJECT_TYPES} ObjType;
|
|
#define OBJMIXPROB 20 /* inverse of the chances of using an odd
|
|
object in the pattern */
|
|
|
|
static const struct {
|
|
DrawProc *draw; /* Object Rendering function */
|
|
int handle; /* Length of object's handle */
|
|
int mintrail; /* Minimum trail length */
|
|
double cor; /* Coefficient of Restitution. perfect bounce = 1 */
|
|
double weight; /* Heavier objects don't get thrown as high */
|
|
} ObjectDefs[] = {
|
|
{ /* Ball */
|
|
show_ball,
|
|
0,
|
|
1,
|
|
0.9,
|
|
1.0,
|
|
},
|
|
{ /* Club */
|
|
show_europeanclub,
|
|
15,
|
|
1,
|
|
0.55, /* Clubs don't bounce too well */
|
|
1.0,
|
|
},
|
|
{ /* Torch */
|
|
show_torch,
|
|
15,
|
|
20, /* Torches need flames */
|
|
0.0, /* Torches don't bounce -- fire risk! */
|
|
1.0,
|
|
},
|
|
{ /* Knife */
|
|
show_knife,
|
|
15,
|
|
1,
|
|
0.0, /* Knives don't bounce */
|
|
1.0,
|
|
},
|
|
{ /* Ring */
|
|
show_ring,
|
|
15,
|
|
1,
|
|
0.8,
|
|
1.0,
|
|
},
|
|
{ /* Bowling Ball */
|
|
show_bball,
|
|
0,
|
|
1,
|
|
0.2,
|
|
5.0,
|
|
},
|
|
};
|
|
|
|
/**************************
|
|
* Trajectory definitions *
|
|
**************************/
|
|
|
|
typedef enum {HEIGHT, ADAM} Notation;
|
|
typedef enum {Empty, Full, Ball} Throwable;
|
|
typedef enum {LEFT, RIGHT} Hand;
|
|
typedef enum {THROW, CATCH} Action;
|
|
typedef enum {HAND, ELBOW, SHOULDER} Joint;
|
|
typedef enum {ATCH, THRATCH, ACTION, LINKEDACTION,
|
|
PTHRATCH, BPREDICTOR, PREDICTOR} TrajectoryStatus;
|
|
typedef struct {double a, b, c, d; } Spline;
|
|
typedef DXPoint Arm[3];
|
|
|
|
/* A Wander contains a Spline and a time interval. A list of Wanders
|
|
* describes the performer's position as he moves around the screen. */
|
|
typedef struct wander *WanderPtr;
|
|
typedef struct wander {
|
|
WanderPtr next, prev;
|
|
double x;
|
|
unsigned long finish;
|
|
Spline s;
|
|
#ifdef MEMTEST
|
|
char pad[1024];
|
|
#endif
|
|
} Wander;
|
|
|
|
/* Object is an arbitrary object being juggled. Each Trajectory
|
|
* references an Object ("count" tracks this), and each Object is also
|
|
* linked into a global Objects list. Objects may include a Trace
|
|
* list for tracking erasures. */
|
|
typedef struct object *ObjectPtr;
|
|
typedef struct object {
|
|
ObjectPtr next, prev;
|
|
|
|
ObjType type;
|
|
int color;
|
|
int count; /* reference count */
|
|
Bool active; /* Object is in use */
|
|
|
|
Trace *trace;
|
|
int tracelen;
|
|
int tail;
|
|
#ifdef MEMTEST
|
|
char pad[1024];
|
|
#endif
|
|
} Object;
|
|
|
|
/* Trajectory is a segment of juggling action. A list of Trajectories
|
|
* defines the juggling performance. The Trajectory list goes through
|
|
* multiple processing steps to convert it from basic juggling
|
|
* notation into rendering data. */
|
|
|
|
typedef struct trajectory *TrajectoryPtr;
|
|
typedef struct trajectory {
|
|
TrajectoryPtr prev, next; /* for building list */
|
|
TrajectoryStatus status;
|
|
|
|
/* Throw */
|
|
char posn;
|
|
int height;
|
|
int adam;
|
|
char *pattern;
|
|
char *name;
|
|
|
|
/* Action */
|
|
Hand hand;
|
|
Action action;
|
|
|
|
/* LinkedAction */
|
|
int color;
|
|
Object *object;
|
|
int divisions;
|
|
double angle, spin;
|
|
TrajectoryPtr balllink;
|
|
TrajectoryPtr handlink;
|
|
|
|
/* PThratch */
|
|
double cx; /* Moving juggler */
|
|
double x, y; /* current position */
|
|
double dx, dy; /* initial velocity */
|
|
|
|
/* Predictor */
|
|
Throwable type;
|
|
unsigned long start, finish;
|
|
Spline xp, yp;
|
|
|
|
#ifdef MEMTEST
|
|
char pad[1024];
|
|
#endif
|
|
} Trajectory;
|
|
|
|
|
|
/* Jugglestruct: per-screen global data. The master Wander, Object
|
|
* and Trajectory lists are anchored here. */
|
|
typedef struct {
|
|
double scale;
|
|
Wander *wander;
|
|
double cx;
|
|
double Gr;
|
|
Trajectory *head;
|
|
Arm arm[2][2];
|
|
char *pattern;
|
|
int count;
|
|
int num_balls;
|
|
time_t begintime; /* should make 'time' usable for at least 48 days
|
|
on a 32-bit machine */
|
|
unsigned long time; /* millisecond timer*/
|
|
ObjType objtypes;
|
|
Object *objects;
|
|
} jugglestruct;
|
|
|
|
static jugglestruct *juggles = (jugglestruct *) NULL;
|
|
|
|
static XFontStruct *mode_font = None;
|
|
|
|
/*******************
|
|
* Pattern Library *
|
|
*******************/
|
|
|
|
typedef struct {
|
|
const char * pattern;
|
|
const char * name;
|
|
} patternstruct;
|
|
|
|
/* List of popular patterns, in any order */
|
|
/* Patterns should be given in Adam notation so the generator can
|
|
concatenate them safely. Null descriptions are ok. Height
|
|
notation will be displayed automatically. */
|
|
static patternstruct portfolio[] = {
|
|
{"[+2 1]", /* +3 1 */ "Typical 2 ball juggler"},
|
|
{"[2 0]", /* 4 0 */ "2 in 1 hand"},
|
|
{"[2 0 1]", /* 5 0 1 */},
|
|
{"[+2 0 +2 0 0]" /* +5 0 +5 0 0 */},
|
|
{"[+2 0 1 2 2]", /* +4 0 1 2 3 */},
|
|
{"[2 0 1 1]", /* 6 0 1 1 */},
|
|
|
|
{"[3]", /* 3 */ "3 cascade"},
|
|
{"[+3]", /* +3 */ "reverse 3 cascade"},
|
|
{"[=3]", /* =3 */ "cascade 3 under arm"},
|
|
{"[&3]", /* &3 */ "cascade 3 catching under arm"},
|
|
{"[_3]", /* _3 */ "bouncing 3 cascade"},
|
|
{"[+3 x3 =3]", /* +3 x3 =3 */ "Mill's mess"},
|
|
{"[3 2 1]", /* 5 3 1" */},
|
|
{"[3 3 1]", /* 4 4 1" */},
|
|
{"[3 1 2]", /* 6 1 2 */ "See-saw"},
|
|
{"[=3 3 1 2]", /* =4 5 1 2 */},
|
|
{"[=3 2 2 3 1 2]", /* =6 2 2 5 1 2 */ "=4 5 1 2 stretched"},
|
|
{"[+3 3 1 3]", /* +4 4 1 3 */ "anemic shower box"},
|
|
{"[3 3 1]", /* 4 4 1 */},
|
|
{"[+3 2 3]", /* +4 2 3 */},
|
|
{"[+3 1]", /* +5 1 */ "3 shower"},
|
|
{"[_3 1]", /* _5 1 */ "bouncing 3 shower"},
|
|
{"[3 0 3 0 3]", /* 5 0 5 0 5 */ "shake 3 out of 5"},
|
|
{"[3 3 3 0 0]", /* 5 5 5 0 0 */ "flash 3 out of 5"},
|
|
{"[3 3 0]", /* 4 5 0 */ "complete waste of a 5 ball juggler"},
|
|
{"[3 3 3 0 0 0 0]", /* 7 7 7 0 0 0 0 */ "3 flash"},
|
|
{"[+3 0 +3 0 +3 0 0]", /* +7 0 +7 0 +7 0 0 */},
|
|
{"[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 */},
|
|
{"[3 0 2 0]", /* 8 0 4 0 */},
|
|
{"[_3 2 1]", /* _5 3 1 */},
|
|
{"[_3 0 1]", /* _8 0 1 */},
|
|
{"[1 _3 1 _3 0 1 _3 0]", /* 1 _7 1 _7 0 1 _7 0 */},
|
|
{"[_3 2 1 _3 1 2 1]", /* _6 3 1 _6 1 3 1 */},
|
|
|
|
{"[4]", /* 4 */ "4 cascade"},
|
|
{"[+4 3]", /* +5 3 */ "4 ball half shower"},
|
|
{"[4 4 2]", /* 5 5 2 */},
|
|
{"[+4 4 4 +4]", /* +4 4 4 +4 */ "4 columns"},
|
|
{"[+4 3 +4]", /* +5 3 +4 */},
|
|
{"[4 3 4 4]", /* 5 3 4 4 */},
|
|
{"[4 3 3 4]", /* 6 3 3 4 */},
|
|
{"[4 3 2 4", /* 6 4 2 4 */},
|
|
{"[+4 1]", /* +7 1 */ "4 shower"},
|
|
{"[4 4 4 4 0]", /* 5 5 5 5 0 */ "learning 5"},
|
|
{"[+4 x4 =4]", /* +4 x4 =4 */ "Mill's mess for 4"},
|
|
{"[+4 2 1 3]", /* +9 3 1 3 */},
|
|
{"[4 4 1 4 1 4]", /* 6 6 1 5 1 5, by Allen Knutson */},
|
|
{"[_4 _4 _4 1 _4 1]", /* _5 _6 _6 1 _5 1 */},
|
|
{"[_4 3 3]", /* _6 3 3 */},
|
|
{"[_4 3 1]", /* _7 4 1 */},
|
|
{"[_4 2 1]", /* _8 3 1 */},
|
|
{"[_4 3 3 3 0]", /* _8 4 4 4 0 */},
|
|
{"[_4 1 3 1]", /* _9 1 5 1 */},
|
|
{"[_4 1 3 1 2]", /* _10 1 6 1 2 */},
|
|
|
|
{"[5]", /* 5 */ "5 cascade"},
|
|
{"[_5 _5 _5 _5 _5 5 5 5 5 5]", /* _5 _5 _5 _5 _5 5 5 5 5 5 */},
|
|
{"[+5 x5 =5]", /* +5 x5 =5 */ "Mill's mess for 5"},
|
|
{"[5 4 4]", /* 7 4 4 */},
|
|
{"[_5 4 4]", /* _7 4 4 */},
|
|
{"[1 2 3 4 5 5 5 5 5]", /* 1 2 3 4 5 6 7 8 9 */ "5 ramp"},
|
|
{"[5 4 5 3 1]", /* 8 5 7 4 1, by Allen Knutson */},
|
|
{"[_5 4 1 +4]", /* _9 5 1 5 */},
|
|
{"[_5 4 +4 +4]", /* _8 4 +4 +4 */},
|
|
{"[_5 4 4 4 1]", /* _9 5 5 5 1 */},
|
|
{"[_5 4 4 5 1]",},
|
|
{"[_5 4 4 +4 4 0]", /*_10 5 5 +5 5 0 */},
|
|
|
|
{"[6]", /* 6 */ "6 cascade"},
|
|
{"[+6 5]", /* +7 5 */},
|
|
{"[6 4]", /* 8 4 */},
|
|
{"[+6 3]", /* +9 3 */},
|
|
{"[6 5 4 4]", /* 9 7 4 4 */},
|
|
{"[+6 5 5 5]", /* +9 5 5 5 */},
|
|
{"[6 0 6]", /* 9 0 9 */},
|
|
{"[_6 0 _6]", /* _9 0 _9 */},
|
|
|
|
{"[_7]", /* _7 */ "bouncing 7 cascade"},
|
|
{"[7]", /* 7 */ "7 cascade"},
|
|
{"[7 6 6 6 6]", /* 11 6 6 6 6 */ "Gatto's High Throw"},
|
|
|
|
};
|
|
|
|
typedef struct { int start; int number; } PatternIndex;
|
|
|
|
static struct {
|
|
int minballs;
|
|
int maxballs;
|
|
PatternIndex index[XtNumber(portfolio)];
|
|
} patternindex;
|
|
|
|
/*******************
|
|
* list management *
|
|
*******************/
|
|
|
|
#define DUP_OBJECT(n, t) { \
|
|
(n)->object = (t)->object; \
|
|
if((n)->object != NULL) (n)->object->count++; \
|
|
}
|
|
|
|
/* t must point to an existing element. t must not be an
|
|
expression ending ->next or ->prev */
|
|
#define REMOVE(t) { \
|
|
(t)->next->prev = (t)->prev; \
|
|
(t)->prev->next = (t)->next; \
|
|
free(t); \
|
|
}
|
|
|
|
/* t receives element to be created and added to the list. ot must
|
|
point to an existing element or be identical to t to start a new
|
|
list. Applicable to Trajectories, Objects and Traces. */
|
|
#define ADD_ELEMENT(type, t, ot) \
|
|
if (((t) = (type*)calloc(1,sizeof(type))) != NULL) { \
|
|
(t)->next = (ot)->next; \
|
|
(t)->prev = (ot); \
|
|
(ot)->next = (t); \
|
|
(t)->next->prev = (t); \
|
|
}
|
|
|
|
static void
|
|
object_destroy(Object* o)
|
|
{
|
|
if(o->trace != NULL) {
|
|
while(o->trace->next != o->trace) {
|
|
Trace *s = o->trace->next;
|
|
REMOVE(s); /* Don't eliminate 's' */
|
|
}
|
|
free(o->trace);
|
|
}
|
|
REMOVE(o);
|
|
}
|
|
|
|
static void
|
|
trajectory_destroy(Trajectory *t) {
|
|
if(t->name != NULL) free(t->name);
|
|
if(t->pattern != NULL) free(t->pattern);
|
|
/* Reduce object link count and call destructor if necessary */
|
|
if(t->object != NULL && --t->object->count < 1 && t->object->tracelen == 0) {
|
|
object_destroy(t->object);
|
|
}
|
|
REMOVE(t); /* Unlink and free */
|
|
}
|
|
|
|
static void
|
|
free_juggle(jugglestruct *sp) {
|
|
if (sp->head != NULL) {
|
|
while (sp->head->next != sp->head) {
|
|
trajectory_destroy(sp->head->next);
|
|
}
|
|
free(sp->head);
|
|
sp->head = (Trajectory *) NULL;
|
|
}
|
|
if(sp->objects != NULL) {
|
|
while (sp->objects->next != sp->objects) {
|
|
object_destroy(sp->objects->next);
|
|
}
|
|
free(sp->objects);
|
|
sp->objects = (Object*)NULL;
|
|
}
|
|
if(sp->wander != NULL) {
|
|
while (sp->wander->next != sp->wander) {
|
|
Wander *w = sp->wander->next;
|
|
REMOVE(w);
|
|
}
|
|
free(sp->wander);
|
|
sp->wander = (Wander*)NULL;
|
|
}
|
|
if(sp->pattern != NULL) {
|
|
free(sp->pattern);
|
|
sp->pattern = NULL;
|
|
}
|
|
}
|
|
|
|
static Bool
|
|
add_throw(jugglestruct *sp, char type, int h, Notation n, const char* name)
|
|
{
|
|
Trajectory *t;
|
|
|
|
ADD_ELEMENT(Trajectory, t, sp->head->prev);
|
|
if(t == NULL){ /* Out of Memory */
|
|
free_juggle(sp);
|
|
return False;
|
|
}
|
|
t->object = NULL;
|
|
if(name != NULL)
|
|
t->name = strdup(name);
|
|
t->posn = type;
|
|
if (n == ADAM) {
|
|
t->adam = h;
|
|
t->height = 0;
|
|
t->status = ATCH;
|
|
} else {
|
|
t->height = h;
|
|
t->status = THRATCH;
|
|
}
|
|
return True;
|
|
}
|
|
|
|
/* add a Thratch to the performance */
|
|
static Bool
|
|
program(ModeInfo *mi, const char *patn, const char *name, int cycles)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
const char *p;
|
|
int w, h, i, seen;
|
|
Notation notation;
|
|
char type;
|
|
|
|
if (MI_IS_VERBOSE(mi)) {
|
|
(void) fprintf(stderr, "juggle[%d]: Programmed: %s x %d\n",
|
|
MI_SCREEN(mi), (name == NULL) ? patn : name, cycles);
|
|
}
|
|
|
|
for(w=i=0; i < cycles; i++, w++) { /* repeat until at least "cycles" throws
|
|
have been programmed */
|
|
/* title is the pattern name to be supplied to the first throw of
|
|
a sequence. If no name if given, use an empty title so that
|
|
the sequences are still delimited. */
|
|
const char *title = (name != NULL)? name : "";
|
|
type=' ';
|
|
h = 0;
|
|
seen = 0;
|
|
notation = HEIGHT;
|
|
for(p=patn; *p; p++) {
|
|
if (*p >= '0' && *p <='9') {
|
|
seen = 1;
|
|
h = 10*h + (*p - '0');
|
|
} else {
|
|
Notation nn = notation;
|
|
switch (*p) {
|
|
case '[': /* begin Adam notation */
|
|
notation = ADAM;
|
|
break;
|
|
case '-': /* Inside throw */
|
|
type = ' ';
|
|
break;
|
|
case '+': /* Outside throw */
|
|
case '=': /* Cross throw */
|
|
case '&': /* Cross catch */
|
|
case 'x': /* Cross throw and catch */
|
|
case '_': /* Bounce */
|
|
case 'k': /* Kickup */
|
|
type = *p;
|
|
break;
|
|
case '*': /* Lose ball */
|
|
seen = 1;
|
|
h = -1;
|
|
/* fall through */
|
|
case ']': /* end Adam notation */
|
|
nn = HEIGHT;
|
|
/* fall through */
|
|
case ' ':
|
|
if (seen) {
|
|
i++;
|
|
if (!add_throw(sp, type, h, notation, title))
|
|
return False;
|
|
title = NULL;
|
|
type=' ';
|
|
h = 0;
|
|
seen = 0;
|
|
}
|
|
notation = nn;
|
|
break;
|
|
default:
|
|
if(w == 0) { /* Only warn on first pass */
|
|
(void) fprintf(stderr,
|
|
"juggle[%d]: Unexpected pattern instruction: '%c'\n",
|
|
MI_SCREEN(mi), *p);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (seen) { /* end of sequence */
|
|
if (!add_throw(sp, type, h, notation, title))
|
|
return False;
|
|
title = NULL;
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
/*
|
|
~~~~\~~~~~\~~~
|
|
\\~\\~\~\\\~~~
|
|
\\~\\\\~\\\~\~
|
|
\\\\\\\\\\\~\\
|
|
|
|
[ 3 3 1 3 4 2 3 1 3 3 4 0 2 1 ]
|
|
|
|
4 4 1 3 12 2 4 1 4 4 13 0 3 1
|
|
|
|
*/
|
|
#define BOUNCEOVER 10
|
|
#define KICKMIN 7
|
|
#define THROWMAX 20
|
|
|
|
/* Convert Adam notation into heights */
|
|
static void
|
|
adam(jugglestruct *sp)
|
|
{
|
|
Trajectory *t, *p;
|
|
for(t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status == ATCH) {
|
|
int a = t->adam;
|
|
t->height = 0;
|
|
for(p = t->next; a > 0; p = p->next) {
|
|
if(p == sp->head) {
|
|
t->height = -9; /* Indicate end of processing for name() */
|
|
return;
|
|
}
|
|
if (p->status != ATCH || p->adam < 0 || p->adam>= a) {
|
|
a--;
|
|
}
|
|
t->height++;
|
|
}
|
|
if(t->height > BOUNCEOVER && t->posn == ' '){
|
|
t->posn = '_'; /* high defaults can be bounced */
|
|
} else if(t->height < 3 && t->posn == '_') {
|
|
t->posn = ' '; /* Can't bounce short throws. */
|
|
}
|
|
if(t->height < KICKMIN && t->posn == 'k'){
|
|
t->posn = ' '; /* Can't kick short throws */
|
|
}
|
|
if(t->height > THROWMAX){
|
|
t->posn = 'k'; /* Use kicks for ridiculously high throws */
|
|
}
|
|
t->status = THRATCH;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Discover converted heights and update the sequence title */
|
|
static void
|
|
name(jugglestruct *sp)
|
|
{
|
|
Trajectory *t, *p;
|
|
char buffer[BUFSIZ];
|
|
char *b;
|
|
for(t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status == THRATCH && t->name != NULL) {
|
|
b = buffer;
|
|
for(p = t; p == t || p->name == NULL; p = p->next) {
|
|
if(p == sp->head || p->height < 0) { /* end of reliable data */
|
|
return;
|
|
}
|
|
if(p->posn == ' ') {
|
|
b += sprintf(b, " %d", p->height);
|
|
} else {
|
|
b += sprintf(b, " %c%d", p->posn, p->height);
|
|
}
|
|
if(b - buffer > 500) break; /* otherwise this could eventually
|
|
overflow. It'll be too big to
|
|
display anyway. */
|
|
}
|
|
if(*t->name != 0) {
|
|
(void) sprintf(b, ", %s", t->name);
|
|
}
|
|
free(t->name); /* Don't need name any more, it's been converted
|
|
to pattern */
|
|
t->name = NULL;
|
|
if(t->pattern != NULL) free(t->pattern);
|
|
t->pattern = strdup(buffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Split Thratch notation into explicit throws and catches.
|
|
Usually Catch follows Throw in same hand, but take care of special
|
|
cases. */
|
|
|
|
/* ..n1.. -> .. LTn RT1 LC RC .. */
|
|
/* ..nm.. -> .. LTn LC RTm RC .. */
|
|
|
|
static Bool
|
|
part(jugglestruct *sp)
|
|
{
|
|
Trajectory *t, *nt, *p;
|
|
Hand hand = (LRAND() & 1) ? RIGHT : LEFT;
|
|
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status > THRATCH) {
|
|
hand = t->hand;
|
|
} else if (t->status == THRATCH) {
|
|
char posn = '=';
|
|
|
|
/* plausibility check */
|
|
if (t->height <= 2 && t->posn == '_') {
|
|
t->posn = ' '; /* no short bounces */
|
|
}
|
|
if (t->height <= 1 && (t->posn == '=' || t->posn == '&')) {
|
|
t->posn = ' '; /* 1's need close catches */
|
|
}
|
|
|
|
switch (t->posn) {
|
|
/* throw catch */
|
|
case ' ': posn = '-'; t->posn = '+'; break;
|
|
case '+': posn = '+'; t->posn = '-'; break;
|
|
case '=': posn = '='; t->posn = '+'; break;
|
|
case '&': posn = '+'; t->posn = '='; break;
|
|
case 'x': posn = '='; t->posn = '='; break;
|
|
case '_': posn = '_'; t->posn = '-'; break;
|
|
case 'k': posn = 'k'; t->posn = 'k'; break;
|
|
default:
|
|
(void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
|
|
break;
|
|
}
|
|
hand = (Hand) ((hand + 1) % 2);
|
|
t->status = ACTION;
|
|
t->hand = hand;
|
|
p = t->prev;
|
|
|
|
if (t->height == 1 && p != sp->head) {
|
|
p = p->prev; /* '1's are thrown earlier than usual */
|
|
}
|
|
|
|
|
|
|
|
t->action = CATCH;
|
|
ADD_ELEMENT(Trajectory, nt, p);
|
|
if(nt == NULL){
|
|
free_juggle(sp);
|
|
return False;
|
|
}
|
|
nt->object = NULL;
|
|
nt->status = ACTION;
|
|
nt->action = THROW;
|
|
nt->height = t->height;
|
|
nt->hand = hand;
|
|
nt->posn = posn;
|
|
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
static ObjType
|
|
choose_object(void) {
|
|
ObjType o;
|
|
for (;;) {
|
|
o = (ObjType)NRAND((ObjType)NUM_OBJECT_TYPES);
|
|
if(balls && o == BALL) break;
|
|
if(clubs && o == CLUB) break;
|
|
if(torches && o == TORCH) break;
|
|
if(knives && o == KNIFE) break;
|
|
if(rings && o == RING) break;
|
|
if(bballs && o == BBALLS) break;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
/* Connnect up throws and catches to figure out which ball goes where.
|
|
Do the same with the juggler's hands. */
|
|
|
|
static void
|
|
lob(ModeInfo *mi)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
Trajectory *t, *p;
|
|
int h;
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status == ACTION) {
|
|
if (t->action == THROW) {
|
|
if (t->type == Empty) {
|
|
/* Create new Object */
|
|
ADD_ELEMENT(Object, t->object, sp->objects);
|
|
t->object->count = 1;
|
|
t->object->tracelen = 0;
|
|
t->object->active = False;
|
|
/* Initialise object's circular trace list */
|
|
ADD_ELEMENT(Trace, t->object->trace, t->object->trace);
|
|
|
|
if (MI_NPIXELS(mi) > 2) {
|
|
t->object->color = 1 + NRAND(MI_NPIXELS(mi) - 2);
|
|
} else {
|
|
#ifdef STANDALONE
|
|
t->object->color = 1;
|
|
#else
|
|
t->object->color = 0;
|
|
#endif
|
|
}
|
|
|
|
/* Small chance of picking a random object instead of the
|
|
current theme. */
|
|
if(NRAND(OBJMIXPROB) == 0) {
|
|
t->object->type = choose_object();
|
|
} else {
|
|
t->object->type = sp->objtypes;
|
|
}
|
|
|
|
/* Check to see if we need trails for this object */
|
|
if(tail < ObjectDefs[t->object->type].mintrail) {
|
|
t->object->tail = ObjectDefs[t->object->type].mintrail;
|
|
} else {
|
|
t->object->tail = tail;
|
|
}
|
|
}
|
|
|
|
/* Balls can change divisions at each throw */
|
|
t->divisions = 2 * (NRAND(2) + 1);
|
|
|
|
/* search forward for next catch in this hand */
|
|
for (p = t->next; t->handlink == NULL; p = p->next) {
|
|
if(p->status < ACTION || p == sp->head) return;
|
|
if (p->action == CATCH) {
|
|
if (t->handlink == NULL && p->hand == t->hand) {
|
|
t->handlink = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (t->height > 0) {
|
|
h = t->height - 1;
|
|
|
|
/* search forward for next ball catch */
|
|
for (p = t->next; t->balllink == NULL; p = p->next) {
|
|
if(p->status < ACTION || p == sp->head) {
|
|
t->handlink = NULL;
|
|
return;
|
|
}
|
|
if (p->action == CATCH) {
|
|
if (t->balllink == NULL && --h < 1) { /* caught */
|
|
t->balllink = p; /* complete trajectory */
|
|
# if 0
|
|
if (p->type == Full) {
|
|
(void) fprintf(stderr, "juggle[%d]: Dropped %d\n",
|
|
MI_SCREEN(mi), t->object->color);
|
|
}
|
|
#endif
|
|
p->type = Full;
|
|
DUP_OBJECT(p, t); /* accept catch */
|
|
p->angle = t->angle;
|
|
p->divisions = t->divisions;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
t->type = Empty; /* thrown */
|
|
} else if (t->action == CATCH) {
|
|
/* search forward for next throw from this hand */
|
|
for (p = t->next; t->handlink == NULL; p = p->next) {
|
|
if(p->status < ACTION || p == sp->head) return;
|
|
if (p->action == THROW && p->hand == t->hand) {
|
|
p->type = t->type; /* pass ball */
|
|
DUP_OBJECT(p, t); /* pass object */
|
|
p->divisions = t->divisions;
|
|
t->handlink = p;
|
|
}
|
|
}
|
|
}
|
|
t->status = LINKEDACTION;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clap when both hands are empty */
|
|
static void
|
|
clap(jugglestruct *sp)
|
|
{
|
|
Trajectory *t, *p;
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status == LINKEDACTION &&
|
|
t->action == CATCH &&
|
|
t->type == Empty &&
|
|
t->handlink != NULL &&
|
|
t->handlink->height == 0) { /* Completely idle hand */
|
|
|
|
for (p = t->next; p != sp->head; p = p->next) {
|
|
if (p->status == LINKEDACTION &&
|
|
p->action == CATCH &&
|
|
p->hand != t->hand) { /* Next catch other hand */
|
|
if(p->type == Empty &&
|
|
p->handlink != NULL &&
|
|
p->handlink->height == 0) { /* Also completely idle */
|
|
|
|
t->handlink->posn = '^'; /* Move first hand's empty throw */
|
|
p->posn = '^'; /* to meet second hand's empty
|
|
catch */
|
|
|
|
}
|
|
break; /* Only need first catch */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CUBIC(s, t) ((((s).a * (t) + (s).b) * (t) + (s).c) * (t) + (s).d)
|
|
|
|
/* Compute single spline from x0 with velocity dx0 at time t0 to x1
|
|
with velocity dx1 at time t1 */
|
|
static Spline
|
|
makeSpline(double x0, double dx0, int t0, double x1, double dx1, int t1)
|
|
{
|
|
Spline s;
|
|
double a, b, c, d;
|
|
double x10;
|
|
double t10;
|
|
|
|
x10 = x1 - x0;
|
|
t10 = t1 - t0;
|
|
a = ((dx0 + dx1)*t10 - 2*x10) / (t10*t10*t10);
|
|
b = (3*x10 - (2*dx0 + dx1)*t10) / (t10*t10);
|
|
c = dx0;
|
|
d = x0;
|
|
s.a = a;
|
|
s.b = -3*a*t0 + b;
|
|
s.c = (3*a*t0 - 2*b)*t0 + c;
|
|
s.d = ((-a*t0 + b)*t0 - c)*t0 +d;
|
|
return s;
|
|
}
|
|
|
|
/* Compute a pair of splines. s1 goes from x0 vith velocity dx0 at
|
|
time t0 to x1 at time t1. s2 goes from x1 at time t1 to x2 with
|
|
velocity dx2 at time t2. The arrival and departure velocities at
|
|
x1, t1 must be the same. */
|
|
static double
|
|
makeSplinePair(Spline *s1, Spline *s2,
|
|
double x0, double dx0, int t0,
|
|
double x1, int t1,
|
|
double x2, double dx2, int t2)
|
|
{
|
|
double x10, x21, t21, t10, t20, dx1;
|
|
x10 = x1 - x0;
|
|
x21 = x2 - x1;
|
|
t21 = t2 - t1;
|
|
t10 = t1 - t0;
|
|
t20 = t2 - t0;
|
|
dx1 = (3*x10*t21*t21 + 3*x21*t10*t10 + 3*dx0*t10*t21*t21
|
|
- dx2*t10*t10*t21 - 4*dx0*t10*t21*t21) /
|
|
(2*t10*t21*t20);
|
|
*s1 = makeSpline(x0, dx0, t0, x1, dx1, t1);
|
|
*s2 = makeSpline(x1, dx1, t1, x2, dx2, t2);
|
|
return dx1;
|
|
}
|
|
|
|
/* Compute a Ballistic path in a pair of degenerate splines. sx goes
|
|
from x at time t at constant velocity dx. sy goes from y at time t
|
|
with velocity dy and constant acceleration g. */
|
|
static void
|
|
makeParabola(Trajectory *n,
|
|
double x, double dx, double y, double dy, double g)
|
|
{
|
|
double t = (double)n->start;
|
|
n->xp.a = 0;
|
|
n->xp.b = 0;
|
|
n->xp.c = dx;
|
|
n->xp.d = -dx*t + x;
|
|
n->yp.a = 0;
|
|
n->yp.b = g/2;
|
|
n->yp.c = -g*t + dy;
|
|
n->yp.d = g/2*t*t - dy*t + y;
|
|
}
|
|
|
|
|
|
|
|
/* Make juggler wander around the screen */
|
|
static double wander(jugglestruct *sp, unsigned long time)
|
|
{
|
|
Wander *w = NULL;
|
|
for (w = sp->wander->next; w != sp->wander; w = w->next) {
|
|
if (w->finish < sp->time) { /* expired */
|
|
Wander *ww = w;
|
|
w = w->prev;
|
|
REMOVE(ww);
|
|
} else if(w->finish > time) {
|
|
break;
|
|
}
|
|
}
|
|
if(w == sp->wander) { /* Need a new one */
|
|
ADD_ELEMENT(Wander, w, sp->wander->prev);
|
|
if(w == NULL) { /* Memory problem */
|
|
return 0.0;
|
|
}
|
|
w->finish = time + 3*THROW_CATCH_INTERVAL + NRAND(10*THROW_CATCH_INTERVAL);
|
|
if(time == 0) {
|
|
w->x = 0;
|
|
} else {
|
|
w->x = w->prev->x * 0.9 + NRAND(40) - 20;
|
|
}
|
|
w->s = makeSpline(w->prev->x, 0.0, w->prev->finish, w->x, 0.0, w->finish);
|
|
}
|
|
return CUBIC(w->s, time);
|
|
}
|
|
|
|
#define SX 25 /* Shoulder Width */
|
|
|
|
/* Convert hand position symbols into actual time/space coordinates */
|
|
static void
|
|
positions(jugglestruct *sp)
|
|
{
|
|
Trajectory *t;
|
|
unsigned long now = sp->time; /* Make sure we're not lost in the past */
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status >= PTHRATCH) {
|
|
now = t->start;
|
|
} else if (t->status == ACTION || t->status == LINKEDACTION) {
|
|
/* Allow ACTIONs to be annotated, but we won't mark them ready
|
|
for the next stage */
|
|
|
|
double xo = 0, yo;
|
|
double sx = SX;
|
|
double pose = SX/2;
|
|
|
|
/* time */
|
|
if (t->action == CATCH) { /* Throw-to-catch */
|
|
if (t->type == Empty) {
|
|
now += (int) THROW_NULL_INTERVAL; /* failed catch is short */
|
|
} else { /* successful catch */
|
|
now += (int)(THROW_CATCH_INTERVAL);
|
|
}
|
|
} else { /* Catch-to-throw */
|
|
if(t->object != NULL) {
|
|
now += (int) (CATCH_THROW_INTERVAL *
|
|
ObjectDefs[t->object->type].weight);
|
|
} else {
|
|
now += (int) (CATCH_THROW_INTERVAL);
|
|
}
|
|
}
|
|
|
|
if(t->start == 0)
|
|
t->start = now;
|
|
else /* Concatenated performances may need clock resync */
|
|
now = t->start;
|
|
|
|
t->cx = wander(sp, t->start);
|
|
|
|
/* space */
|
|
yo = 90;
|
|
|
|
/* Add room for the handle */
|
|
if(t->action == CATCH && t->object != NULL)
|
|
yo -= ObjectDefs[t->object->type].handle;
|
|
|
|
switch (t->posn) {
|
|
case '-': xo = sx - pose; break;
|
|
case '_':
|
|
case 'k':
|
|
case '+': xo = sx + pose; break;
|
|
case '~':
|
|
case '=': xo = - sx - pose; yo += pose; break;
|
|
case '^': xo = 0; yo += pose*2; break; /* clap */
|
|
default:
|
|
(void) fprintf(stderr, "juggle: unexpected posn %c\n", t->posn);
|
|
break;
|
|
}
|
|
|
|
t->angle = (((t->hand == LEFT) ^
|
|
(t->posn == '+' || t->posn == '_' || t->posn == 'k' ))?
|
|
-1 : 1) * M_PI/2;
|
|
|
|
t->x = t->cx + ((t->hand == LEFT) ? xo : -xo);
|
|
t->y = yo;
|
|
|
|
/* Only mark complete if it was already linked */
|
|
if(t->status == LINKEDACTION) {
|
|
t->status = PTHRATCH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Private physics functions */
|
|
|
|
/* Compute the spin-rate for a trajectory. Different types of throw
|
|
(eg, regular thows, bounces, kicks, etc) have different spin
|
|
requirements.
|
|
|
|
type = type of object
|
|
h = trajectory of throwing hand (throws), or next throwing hand (catches)
|
|
old = earlier spin to consider
|
|
dt = time span of this trajectory
|
|
height = height of ball throw or 0 if based on old spin
|
|
turns = full club turns required during this operation
|
|
togo = partial club turns required to match hands
|
|
*/
|
|
static double
|
|
spinrate(ObjType type, Trajectory *h, double old, double dt,
|
|
int height, int turns, double togo)
|
|
{
|
|
const int dir = (h->hand == LEFT) ^ (h->posn == '+')? -1 : 1;
|
|
|
|
if(ObjectDefs[type].handle != 0) { /* Clubs */
|
|
return (dir * turns * 2 * M_PI + togo) / dt;
|
|
} else if(height == 0) { /* Balls already spinning */
|
|
return old/2;
|
|
} else { /* Balls */
|
|
return dir * NRAND(height*10)/20/ObjectDefs[type].weight * 2 * M_PI / dt;
|
|
}
|
|
}
|
|
|
|
|
|
/* compute the angle at the end of a spinning trajectory */
|
|
static double
|
|
end_spin(Trajectory *t)
|
|
{
|
|
return t->angle + t->spin * (t->finish - t->start);
|
|
}
|
|
|
|
/* Sets the initial angle of the catch following hand movement t to
|
|
the final angle of the throw n. Also sets the angle of the
|
|
subsequent throw to the same angle plus half a turn. */
|
|
static void
|
|
match_spins_on_catch(Trajectory *t, Trajectory *n)
|
|
{
|
|
if(ObjectDefs[t->balllink->object->type].handle == 0) {
|
|
t->balllink->angle = end_spin(n);
|
|
if(t->balllink->handlink != NULL) {
|
|
t->balllink->handlink->angle = t->balllink->angle + M_PI;
|
|
}
|
|
}
|
|
}
|
|
|
|
static double
|
|
find_bounce(jugglestruct *sp,
|
|
double yo, double yf, double yc, double tc, double cor)
|
|
{
|
|
double tb, i, dy = 0;
|
|
const double e = 1; /* permissible error in yc */
|
|
|
|
/*
|
|
tb = time to bounce
|
|
yt = height at catch time after one bounce
|
|
one or three roots according to timing
|
|
find one by interval bisection
|
|
*/
|
|
tb = tc;
|
|
for(i = tc / 2; i > 0.0001; i/=2){
|
|
double dt, yt;
|
|
if(tb == 0){
|
|
(void) fprintf(stderr, "juggle: bounce div by zero!\n");
|
|
break;
|
|
}
|
|
dy = (yf - yo)/tb + sp->Gr/2*tb;
|
|
dt = tc - tb;
|
|
yt = -cor*dy*dt + sp->Gr/2*dt*dt + yf;
|
|
if(yt < yc + e){
|
|
tb-=i;
|
|
}else if(yt > yc - e){
|
|
tb+=i;
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
if(dy*THROW_CATCH_INTERVAL < -200) { /* bounce too hard */
|
|
tb = -1;
|
|
}
|
|
return tb;
|
|
}
|
|
|
|
static Trajectory*
|
|
new_predictor(const Trajectory *t, int start, int finish, double angle)
|
|
{
|
|
Trajectory *n;
|
|
ADD_ELEMENT(Trajectory, n, t->prev);
|
|
if(n == NULL){
|
|
return NULL;
|
|
}
|
|
DUP_OBJECT(n, t);
|
|
n->divisions = t->divisions;
|
|
n->type = Ball;
|
|
n->status = PREDICTOR;
|
|
|
|
n->start = start;
|
|
n->finish = finish;
|
|
n->angle = angle;
|
|
return n;
|
|
}
|
|
|
|
/* Turn abstract timings into physically appropriate object trajectories. */
|
|
static Bool
|
|
projectile(jugglestruct *sp)
|
|
{
|
|
Trajectory *t;
|
|
const int yf = 0; /* Floor height */
|
|
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if (t->status != PTHRATCH || t->action != THROW) {
|
|
continue;
|
|
} else if (t->balllink == NULL) { /* Zero Throw */
|
|
t->status = BPREDICTOR;
|
|
} else if (t->balllink->handlink == NULL) { /* Incomplete */
|
|
return True;
|
|
} else if(t->balllink == t->handlink) {
|
|
/* '2' height - hold on to ball. Don't need to consider
|
|
flourishes, 'hands' will do that automatically anyway */
|
|
|
|
t->type = Full;
|
|
/* Zero spin to avoid wrist injuries */
|
|
t->spin = 0;
|
|
match_spins_on_catch(t, t);
|
|
t->dx = t->dy = 0;
|
|
t->status = BPREDICTOR;
|
|
continue;
|
|
} else {
|
|
if (t->posn == '_') { /* Bounce once */
|
|
|
|
const int tb = (int) (t->start +
|
|
find_bounce(sp, t->y, (double) yf, t->balllink->y,
|
|
(double) (t->balllink->start - t->start),
|
|
ObjectDefs[t->object->type].cor));
|
|
|
|
if(tb < t->start) { /* bounce too hard */
|
|
t->posn = '+'; /* Use regular throw */
|
|
} else {
|
|
Trajectory *n; /* First (throw) trajectory. */
|
|
double dt; /* Time span of a trajectory */
|
|
double dy; /* Distance span of a follow-on trajectory.
|
|
First trajectory uses t->dy */
|
|
/* dx is constant across both trajectories */
|
|
t->dx = (t->balllink->x - t->x) / (t->balllink->start - t->start);
|
|
|
|
{ /* ball follows parabola down */
|
|
n = new_predictor(t, t->start, tb, t->angle);
|
|
if(n == NULL) return False;
|
|
dt = n->finish - n->start;
|
|
/* Ball rate 4, no flight or matching club turns */
|
|
n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0, 0.0);
|
|
t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
|
|
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
|
|
}
|
|
|
|
{ /* ball follows parabola up */
|
|
Trajectory *m = new_predictor(t, n->finish, t->balllink->start,
|
|
end_spin(n));
|
|
if(m == NULL) return False;
|
|
dt = m->finish - m->start;
|
|
/* Use previous ball rate, no flight club turns */
|
|
m->spin = spinrate(t->object->type, t, n->spin, dt, 0, 0,
|
|
t->balllink->angle - m->angle);
|
|
match_spins_on_catch(t, m);
|
|
dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
|
|
makeParabola(m, t->balllink->x - t->dx * dt,
|
|
t->dx, (double) yf, dy, sp->Gr);
|
|
}
|
|
|
|
t->status = BPREDICTOR;
|
|
continue;
|
|
}
|
|
} else if (t->posn == 'k') { /* Drop & Kick */
|
|
Trajectory *n; /* First (drop) trajectory. */
|
|
Trajectory *o; /* Second (rest) trajectory */
|
|
Trajectory *m; /* Third (kick) trajectory */
|
|
const int td = t->start + 2*THROW_CATCH_INTERVAL; /* Drop time */
|
|
const int tk = t->balllink->start - 5*THROW_CATCH_INTERVAL; /* Kick */
|
|
double dt, dy;
|
|
|
|
{ /* Fall to ground */
|
|
n = new_predictor(t, t->start, td, t->angle);
|
|
if(n == NULL) return False;
|
|
dt = n->finish - n->start;
|
|
/* Ball spin rate 4, no flight club turns */
|
|
n->spin = spinrate(t->object->type, t, 0.0, dt, 4, 0,
|
|
t->balllink->angle - n->angle);
|
|
t->dx = (t->balllink->x - t->x) / dt;
|
|
t->dy = (yf - t->y)/dt - sp->Gr/2*dt;
|
|
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
|
|
}
|
|
|
|
{ /* Rest on ground */
|
|
o = new_predictor(t, n->finish, tk, end_spin(n));
|
|
if(o == NULL) return False;
|
|
o->spin = 0;
|
|
makeParabola(o, t->balllink->x, 0.0, (double) yf, 0.0, 0.0);
|
|
}
|
|
|
|
/* Kick up */
|
|
{
|
|
m = new_predictor(t, o->finish, t->balllink->start, end_spin(o));
|
|
if(m == NULL) return False;
|
|
dt = m->finish - m->start;
|
|
/* Match receiving hand, ball rate 4, one flight club turn */
|
|
m->spin = spinrate(t->object->type, t->balllink->handlink, 0.0, dt,
|
|
4, 1, t->balllink->angle - m->angle);
|
|
match_spins_on_catch(t, m);
|
|
dy = (t->balllink->y - yf)/dt - sp->Gr/2 * dt;
|
|
makeParabola(m, t->balllink->x, 0.0, (double) yf, dy, sp->Gr);
|
|
}
|
|
|
|
t->status = BPREDICTOR;
|
|
continue;
|
|
}
|
|
|
|
/* Regular flight, no bounce */
|
|
{ /* ball follows parabola */
|
|
double dt;
|
|
Trajectory *n = new_predictor(t, t->start,
|
|
t->balllink->start, t->angle);
|
|
if(n == NULL) return False;
|
|
dt = t->balllink->start - t->start;
|
|
/* Regular spin */
|
|
n->spin = spinrate(t->object->type, t, 0.0, dt, t->height, t->height/2,
|
|
t->balllink->angle - n->angle);
|
|
match_spins_on_catch(t, n);
|
|
t->dx = (t->balllink->x - t->x) / dt;
|
|
t->dy = (t->balllink->y - t->y) / dt - sp->Gr/2 * dt;
|
|
makeParabola(n, t->x, t->dx, t->y, t->dy, sp->Gr);
|
|
}
|
|
|
|
t->status = BPREDICTOR;
|
|
}
|
|
}
|
|
return True;
|
|
}
|
|
|
|
/* Turn abstract hand motions into cubic splines. */
|
|
static void
|
|
hands(jugglestruct *sp)
|
|
{
|
|
Trajectory *t, *u, *v;
|
|
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
/* no throw => no velocity */
|
|
if (t->status != BPREDICTOR) {
|
|
continue;
|
|
}
|
|
|
|
u = t->handlink;
|
|
if (u == NULL) { /* no next catch */
|
|
continue;
|
|
}
|
|
v = u->handlink;
|
|
if (v == NULL) { /* no next throw */
|
|
continue;
|
|
}
|
|
|
|
/* double spline takes hand from throw, thru catch, to
|
|
next throw */
|
|
|
|
t->finish = u->start;
|
|
t->status = PREDICTOR;
|
|
|
|
u->finish = v->start;
|
|
u->status = PREDICTOR;
|
|
|
|
|
|
/* FIXME: These adjustments leave a small glitch when alternating
|
|
balls and clubs. Just hope no-one notices. :-) */
|
|
|
|
/* make sure empty hand spin matches the thrown object in case it
|
|
had a handle */
|
|
|
|
t->spin = ((t->hand == LEFT)? -1 : 1 ) *
|
|
fabs((u->angle - t->angle)/(u->start - t->start));
|
|
|
|
u->spin = ((v->hand == LEFT) ^ (v->posn == '+')? -1 : 1 ) *
|
|
fabs((v->angle - u->angle)/(v->start - u->start));
|
|
|
|
(void) makeSplinePair(&t->xp, &u->xp,
|
|
t->x, t->dx, t->start,
|
|
u->x, u->start,
|
|
v->x, v->dx, v->start);
|
|
(void) makeSplinePair(&t->yp, &u->yp,
|
|
t->y, t->dy, t->start,
|
|
u->y, u->start,
|
|
v->y, v->dy, v->start);
|
|
|
|
t->status = PREDICTOR;
|
|
}
|
|
}
|
|
|
|
/* Given target x, y find_elbow puts hand at target if possible,
|
|
* otherwise makes hand point to the target */
|
|
static void
|
|
find_elbow(int armlength, DXPoint *h, DXPoint *e, DXPoint *p, DXPoint *s,
|
|
int z)
|
|
{
|
|
double r, h2, t;
|
|
double x = p->x - s->x;
|
|
double y = p->y - s->y;
|
|
h2 = x*x + y*y + z*z;
|
|
if (h2 > 4 * armlength * armlength) {
|
|
t = armlength/sqrt(h2);
|
|
e->x = t*x + s->x;
|
|
e->y = t*y + s->y;
|
|
h->x = 2 * t * x + s->x;
|
|
h->y = 2 * t * y + s->y;
|
|
} else {
|
|
r = sqrt((double)(x*x + z*z));
|
|
t = sqrt(4 * armlength * armlength / h2 - 1);
|
|
e->x = x*(1 + y*t/r)/2 + s->x;
|
|
e->y = (y - r*t)/2 + s->y;
|
|
h->x = x + s->x;
|
|
h->y = y + s->y;
|
|
}
|
|
}
|
|
|
|
|
|
/* NOTE: returned x, y adjusted for arm reach */
|
|
static void
|
|
reach_arm(ModeInfo * mi, Hand side, DXPoint *p)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
DXPoint h, e;
|
|
find_elbow(40, &h, &e, p, &sp->arm[1][side][SHOULDER], 25);
|
|
*p = sp->arm[1][side][HAND] = h;
|
|
sp->arm[1][side][ELBOW] = e;
|
|
}
|
|
|
|
#if DEBUG
|
|
/* dumps a human-readable rendition of the current state of the juggle
|
|
pipeline to stderr for debugging */
|
|
static void
|
|
dump(jugglestruct *sp)
|
|
{
|
|
Trajectory *t;
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
switch (t->status) {
|
|
case ATCH:
|
|
(void) fprintf(stderr, "%p a %c%d\n", (void*)t, t->posn, t->adam);
|
|
break;
|
|
case THRATCH:
|
|
(void) fprintf(stderr, "%p T %c%d %s\n", (void*)t, t->posn, t->height,
|
|
t->pattern == NULL?"":t->pattern);
|
|
break;
|
|
case ACTION:
|
|
if (t->action == CATCH)
|
|
(void) fprintf(stderr, "%p A %c%cC\n",
|
|
(void*)t, t->posn,
|
|
t->hand ? 'R' : 'L');
|
|
else
|
|
(void) fprintf(stderr, "%p A %c%c%c%d\n",
|
|
(void*)t, t->posn,
|
|
t->hand ? 'R' : 'L',
|
|
(t->action == THROW)?'T':'N',
|
|
t->height);
|
|
break;
|
|
case LINKEDACTION:
|
|
(void) fprintf(stderr, "%p L %c%c%c%d %d %p %p\n",
|
|
(void*)t, t->posn,
|
|
t->hand?'R':'L',
|
|
(t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
|
|
t->height, t->object == NULL?0:t->object->color,
|
|
(void*)t->handlink, (void*)t->balllink);
|
|
break;
|
|
case PTHRATCH:
|
|
(void) fprintf(stderr, "%p O %c%c%c%d %d %2d %6lu %6lu\n",
|
|
(void*)t, t->posn,
|
|
t->hand?'R':'L',
|
|
(t->action == THROW)?'T':(t->action == CATCH?'C':'N'),
|
|
t->height, t->type, t->object == NULL?0:t->object->color,
|
|
t->start, t->finish);
|
|
break;
|
|
case BPREDICTOR:
|
|
(void) fprintf(stderr, "%p B %c %2d %6lu %6lu %g\n",
|
|
(void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
|
|
t->object == NULL?0:t->object->color,
|
|
t->start, t->finish, t->yp.c);
|
|
break;
|
|
case PREDICTOR:
|
|
(void) fprintf(stderr, "%p P %c %2d %6lu %6lu %g\n",
|
|
(void*)t, t->type == Ball?'b':t->type == Empty?'e':'f',
|
|
t->object == NULL?0:t->object->color,
|
|
t->start, t->finish, t->yp.c);
|
|
break;
|
|
default:
|
|
(void) fprintf(stderr, "%p: status %d not implemented\n",
|
|
(void*)t, t->status);
|
|
break;
|
|
}
|
|
}
|
|
(void) fprintf(stderr, "---\n");
|
|
}
|
|
#endif
|
|
|
|
static int get_num_balls(const char *j)
|
|
{
|
|
int balls = 0;
|
|
const char *p;
|
|
int h = 0;
|
|
for (p = j; *p; p++) {
|
|
if (*p >= '0' && *p <='9') { /* digit */
|
|
h = 10*h + (*p - '0');
|
|
} else {
|
|
if (h > balls) {
|
|
balls = h;
|
|
}
|
|
h = 0;
|
|
}
|
|
}
|
|
return balls;
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
static int
|
|
compare_num_balls(const void *p1, const void *p2)
|
|
{
|
|
int i, j;
|
|
i = get_num_balls(((patternstruct*)p1)->pattern);
|
|
j = get_num_balls(((patternstruct*)p2)->pattern);
|
|
if (i > j) {
|
|
return (1);
|
|
} else if (i < j) {
|
|
return (-1);
|
|
} else {
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
|
|
/**************************************************************************
|
|
* Rendering Functions *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
static void
|
|
show_arms(ModeInfo * mi, unsigned long color)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
unsigned int i, j;
|
|
Hand side;
|
|
XPoint a[XtNumber(sp->arm[0][0])];
|
|
if(color == MI_BLACK_PIXEL(mi)) {
|
|
j = 0;
|
|
} else {
|
|
j = 1;
|
|
}
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
|
|
ARMWIDTH, LineSolid, CapRound, JoinRound);
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
for(side = LEFT; side <= RIGHT; side = (Hand)((int)side + 1)) {
|
|
/* Translate into device coords */
|
|
for(i = 0; i < XtNumber(a); i++) {
|
|
a[i].x = (short)(MI_WIDTH(mi)/2 + sp->arm[j][side][i].x*sp->scale);
|
|
a[i].y = (short)(MI_HEIGHT(mi) - sp->arm[j][side][i].y*sp->scale);
|
|
if(j == 1)
|
|
sp->arm[0][side][i] = sp->arm[1][side][i];
|
|
}
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
a, XtNumber(a), CoordModeOrigin);
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_figure(ModeInfo * mi, unsigned long color, Bool init)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
XPoint p[7];
|
|
unsigned int i;
|
|
|
|
/* +-----+ 9
|
|
| 6 |
|
|
10 +--+--+
|
|
2 +---+---+ 3
|
|
\ 5 /
|
|
\ /
|
|
\ /
|
|
1 +
|
|
/ \
|
|
/ \
|
|
0 +-----+ 4
|
|
| |
|
|
| |
|
|
| |
|
|
7 + + 8
|
|
*/
|
|
|
|
static const XPoint figure[] = {
|
|
{ 15, 70}, /* 0 Left Hip */
|
|
{ 0, 90}, /* 1 Waist */
|
|
{ SX, 130}, /* 2 Left Shoulder */
|
|
{-SX, 130}, /* 3 Right Shoulder */
|
|
{-15, 70}, /* 4 Right Hip */
|
|
{ 0, 130}, /* 5 Neck */
|
|
{ 0, 140}, /* 6 Chin */
|
|
{ SX, 0}, /* 7 Left Foot */
|
|
{-SX, 0}, /* 8 Right Foot */
|
|
{-17, 174}, /* 9 Head1 */
|
|
{ 17, 140}, /* 10 Head2 */
|
|
};
|
|
XPoint a[XtNumber(figure)];
|
|
|
|
/* Translate into device coords */
|
|
for(i = 0; i < XtNumber(figure); i++) {
|
|
a[i].x = (short)(MI_WIDTH(mi)/2 + (sp->cx + figure[i].x)*sp->scale);
|
|
a[i].y = (short)(MI_HEIGHT(mi) - figure[i].y*sp->scale);
|
|
}
|
|
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
|
|
ARMWIDTH, LineSolid, CapRound, JoinRound);
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
|
|
i = 0; /* Body */
|
|
p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[2];
|
|
p[i++] = a[3]; p[i++] = a[1]; p[i++] = a[4];
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, CoordModeOrigin);
|
|
|
|
i = 0; /* Legs */
|
|
p[i++] = a[7]; p[i++] = a[0]; p[i++] = a[4]; p[i++] = a[8];
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, CoordModeOrigin);
|
|
|
|
i = 0; /* Neck */
|
|
p[i++] = a[5]; p[i++] = a[6];
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, CoordModeOrigin);
|
|
|
|
/* Head */
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
a[9].x, a[9].y,
|
|
a[10].x - a[9].x, a[10].y - a[9].y, 0, 64*360);
|
|
sp->arm[1][LEFT][SHOULDER].x = sp->cx + figure[2].x;
|
|
sp->arm[1][RIGHT][SHOULDER].x = sp->cx + figure[3].x;
|
|
if(init) {
|
|
/* Initialise arms */
|
|
unsigned int i;
|
|
for(i = 0; i < 2; i++){
|
|
sp->arm[i][LEFT][SHOULDER].y = figure[2].y;
|
|
sp->arm[i][LEFT][ELBOW].x = figure[2].x;
|
|
sp->arm[i][LEFT][ELBOW].y = figure[1].y;
|
|
sp->arm[i][LEFT][HAND].x = figure[0].x;
|
|
sp->arm[i][LEFT][HAND].y = figure[1].y;
|
|
sp->arm[i][RIGHT][SHOULDER].y = figure[3].y;
|
|
sp->arm[i][RIGHT][ELBOW].x = figure[3].x;
|
|
sp->arm[i][RIGHT][ELBOW].y = figure[1].y;
|
|
sp->arm[i][RIGHT][HAND].x = figure[4].x;
|
|
sp->arm[i][RIGHT][HAND].y = figure[1].y;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_ball(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
int offset = (int)(s->angle*64*180/M_PI);
|
|
short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
|
|
short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
if (s->divisions == 0) {
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
0, 23040);
|
|
} else if (s->divisions == 4) { /* 90 degree divisions */
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
offset % 23040, 5760);
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
(offset + 11520) % 23040, 5760);
|
|
if (color != MI_BLACK_PIXEL(mi))
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
(offset + 5760) % 23040, 5760);
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
(offset + 17280) % 23040, 5760);
|
|
} else if (s->divisions == 2) { /* 180 degree divisions */
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
offset % 23040, 11520);
|
|
if (color != MI_BLACK_PIXEL(mi))
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
x - BALLRADIUS, y - BALLRADIUS,
|
|
2*BALLRADIUS, 2*BALLRADIUS,
|
|
(offset + 11520) % 23040, 11520);
|
|
} else {
|
|
(void) fprintf(stderr, "juggle[%d]: unexpected divisions: %d\n",
|
|
MI_SCREEN(mi), s->divisions);
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_europeanclub(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
XPoint p[4];
|
|
const double sa = sin(s->angle);
|
|
const double ca = cos(s->angle);
|
|
unsigned int i;
|
|
|
|
/* 6 6
|
|
+-+
|
|
/ \
|
|
4 +-----+ 7
|
|
////////\
|
|
3 +---------+ 8
|
|
2 +---------+ 9
|
|
|///////|
|
|
1 +-------+ 10
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
+-+
|
|
0 11 */
|
|
|
|
static const XPoint club[] = {
|
|
{-24, 2}, /* 0 */
|
|
{-10, 3}, /* 1 */
|
|
{ 1, 6}, /* 2 */
|
|
{ 8, 6}, /* 3 */
|
|
{ 14, 4}, /* 4 */
|
|
{ 16, 3}, /* 5 */
|
|
{ 16,-3}, /* 6 */
|
|
{ 14,-4}, /* 7 */
|
|
{ 8,-6}, /* 8 */
|
|
{ 1,-6}, /* 9 */
|
|
{-10,-3}, /* 10 */
|
|
{-24,-2}, /* 11 */
|
|
{-24, 2}, /* 0 close boundary */
|
|
};
|
|
XPoint a[XtNumber(club)];
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
/* Translate and fake perspective */
|
|
for(i = 0; i < XtNumber(club); i++) {
|
|
a[i].x = (short)(MI_WIDTH(mi)/2 +
|
|
(s->x + club[i].x*PERSPEC*sa)*sp->scale -
|
|
club[i].y*sqrt(sp->scale)*ca);
|
|
a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
|
|
club[i].y*sa*sqrt(sp->scale));
|
|
}
|
|
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
/* Outline in black */
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
|
|
LineSolid, CapRound, JoinRound);
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
a, XtNumber(a), CoordModeOrigin);
|
|
}
|
|
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
|
|
/* Don't be tempted to optimize erase by drawing all the black in
|
|
one X operation. It must use the same ops as the colours to
|
|
guarantee a clean erase. */
|
|
|
|
i = 0; /* Colored stripes */
|
|
p[i++] = a[1]; p[i++] = a[2];
|
|
p[i++] = a[9]; p[i++] = a[10];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
i = 0;
|
|
p[i++] = a[3]; p[i++] = a[4];
|
|
p[i++] = a[7]; p[i++] = a[8];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
}
|
|
|
|
i = 0; /* White center band */
|
|
p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[8]; p[i++] = a[9];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
|
|
i = 0; /* White handle */
|
|
p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[10]; p[i++] = a[11];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
|
|
i = 0; /* White tip */
|
|
p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6]; p[i++] = a[7];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
show_jugglebugclub(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
XPoint p[6];
|
|
const double sa = sin(s->angle);
|
|
const double ca = cos(s->angle);
|
|
unsigned int i;
|
|
|
|
/* 4 5
|
|
+-+
|
|
/ \
|
|
3 +-----+ 6
|
|
////////\
|
|
2 +/////////+ 7
|
|
|///////|
|
|
1 +-------+ 8
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
| |
|
|
+-+
|
|
0 9 */
|
|
|
|
static const XPoint club[] = {
|
|
{-24, 2}, /* 0 */
|
|
{ -9, 3}, /* 1 */
|
|
{ 5, 6}, /* 2 */
|
|
{ 11, 4}, /* 3 */
|
|
{ 16, 3}, /* 4 */
|
|
{ 16,-3}, /* 5 */
|
|
{ 11,-4}, /* 6 */
|
|
{ 5,-6}, /* 7 */
|
|
{ -9,-3}, /* 8 */
|
|
{-24,-2}, /* 9 */
|
|
{-24, 2}, /* 0 close boundary */
|
|
};
|
|
XPoint a[XtNumber(club)];
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
/* Translate and fake perspective */
|
|
for(i = 0; i < XtNumber(club); i++) {
|
|
a[i].x = (short)(MI_WIDTH(mi)/2 +
|
|
(s->x + club[i].x*PERSPEC*sa)*sp->scale -
|
|
club[i].y*sqrt(sp->scale)*ca);
|
|
a[i].y = (short)(MI_HEIGHT(mi) - (s->y - club[i].x*ca)*sp->scale +
|
|
club[i].y*sa*sqrt(sp->scale));
|
|
}
|
|
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
/* Outline in black */
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
|
|
LineSolid, CapRound, JoinRound);
|
|
XDrawLines(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
a, XtNumber(a), CoordModeOrigin);
|
|
}
|
|
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
|
|
/* Don't be tempted to optimize erase by drawing all the black in
|
|
one X operation. It must use the same ops as the colours to
|
|
guarantee a clean erase. */
|
|
|
|
i = 0; /* Coloured center band */
|
|
p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3];
|
|
p[i++] = a[6]; p[i++] = a[7]; p[i++] = a[8];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
}
|
|
|
|
i = 0; /* White handle */
|
|
p[i++] = a[0]; p[i++] = a[1]; p[i++] = a[8]; p[i++] = a[9];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
|
|
i = 0; /* White tip */
|
|
p[i++] = a[3]; p[i++] = a[4]; p[i++] = a[5]; p[i++] = a[6];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
show_torch(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
XPoint head, tail, last;
|
|
DXPoint dhead, dlast;
|
|
const double sa = sin(s->angle);
|
|
const double ca = cos(s->angle);
|
|
|
|
const double TailLen = -24;
|
|
const double HeadLen = 16;
|
|
const short Width = (short)(5 * sqrt(sp->scale));
|
|
|
|
/*
|
|
+///+ head
|
|
last |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+ tail
|
|
*/
|
|
|
|
dhead.x = s->x + HeadLen * PERSPEC * sa;
|
|
dhead.y = s->y - HeadLen * ca;
|
|
|
|
if(color == MI_BLACK_PIXEL(mi)) { /* Use 'last' when erasing */
|
|
dlast = s->dlast;
|
|
} else { /* Store 'last' so we can use it later when s->prev has
|
|
gone */
|
|
if(s->prev != s->next) {
|
|
dlast.x = s->prev->x + HeadLen * PERSPEC * sin(s->prev->angle);
|
|
dlast.y = s->prev->y - HeadLen * cos(s->prev->angle);
|
|
} else {
|
|
dlast = dhead;
|
|
}
|
|
s->dlast = dlast;
|
|
}
|
|
|
|
/* Avoid wrapping (after last is stored) */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
head.x = (short)(MI_WIDTH(mi)/2 + dhead.x*sp->scale);
|
|
head.y = (short)(MI_HEIGHT(mi) - dhead.y*sp->scale);
|
|
|
|
last.x = (short)(MI_WIDTH(mi)/2 + dlast.x*sp->scale);
|
|
last.y = (short)(MI_HEIGHT(mi) - dlast.y*sp->scale);
|
|
|
|
tail.x = (short)(MI_WIDTH(mi)/2 +
|
|
(s->x + TailLen * PERSPEC * sa)*sp->scale );
|
|
tail.y = (short)(MI_HEIGHT(mi) - (s->y - TailLen * ca)*sp->scale );
|
|
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
|
|
Width, LineSolid, CapRound, JoinRound);
|
|
XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
head.x, head.y, tail.x, tail.y);
|
|
}
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
|
|
Width * 2, LineSolid, CapRound, JoinRound);
|
|
|
|
XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
head.x, head.y, last.x, last.y);
|
|
|
|
}
|
|
|
|
static void
|
|
show_knife(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
unsigned int i;
|
|
const double sa = sin(s->angle);
|
|
const double ca = cos(s->angle);
|
|
|
|
/*
|
|
2 +
|
|
|+ 3
|
|
||
|
|
1 +++ 5
|
|
|4|
|
|
| |
|
|
+ 0
|
|
*/
|
|
static const XPoint knife[] = {
|
|
{-24, 0}, /* 0 */
|
|
{ -5,-3}, /* 1 */
|
|
{ 16,-3}, /* 2 */
|
|
{ 12, 0}, /* 3 */
|
|
{ -5, 0}, /* 4 */
|
|
{ -5, 3}, /* 5 */
|
|
};
|
|
XPoint a[XtNumber(knife)], p[5];
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
/* Translate and fake perspective */
|
|
for(i = 0; i < XtNumber(knife); i++) {
|
|
a[i].x = (short)(MI_WIDTH(mi)/2 +
|
|
(s->x + knife[i].x*PERSPEC*sa)*sp->scale -
|
|
knife[i].y*sqrt(sp->scale)*ca*PERSPEC);
|
|
a[i].y = (short)(MI_HEIGHT(mi) - (s->y - knife[i].x*ca)*sp->scale +
|
|
knife[i].y*sa*sqrt(sp->scale));
|
|
}
|
|
|
|
/* Handle */
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), (short)(4*sqrt(sp->scale)),
|
|
LineSolid, CapRound, JoinRound);
|
|
XDrawLine(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
a[0].x, a[0].y, a[4].x, a[4].y);
|
|
|
|
/* Blade */
|
|
if(color != MI_BLACK_PIXEL(mi)) {
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
}
|
|
i = 0;
|
|
p[i++] = a[1]; p[i++] = a[2]; p[i++] = a[3]; p[i++] = a[5];
|
|
XFillPolygon(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
p, i, Convex, CoordModeOrigin);
|
|
}
|
|
|
|
static void
|
|
show_ring(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
|
|
short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
|
|
double radius = 15 * sp->scale;
|
|
short thickness = (short)(8 * sqrt(sp->scale));
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi),
|
|
thickness, LineSolid, CapRound, JoinRound);
|
|
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius*PERSPEC), (short)(y - radius),
|
|
(short)(2*radius*PERSPEC), (short)(2*radius),
|
|
0, 23040);
|
|
}
|
|
|
|
|
|
static void
|
|
show_bball(ModeInfo *mi, unsigned long color, Trace *s)
|
|
{
|
|
jugglestruct *sp = &juggles[MI_SCREEN(mi)];
|
|
short x = (short)(MI_WIDTH(mi)/2 + s->x * sp->scale);
|
|
short y = (short)(MI_HEIGHT(mi) - s->y * sp->scale);
|
|
double radius = 12 * sp->scale;
|
|
int offset = (int)(s->angle*64*180/M_PI);
|
|
int holesize = (int)(3.0*sqrt(sp->scale));
|
|
|
|
/* Avoid wrapping */
|
|
if(s->y*sp->scale > MI_HEIGHT(mi) * 2) return;
|
|
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
|
|
XFillArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius), (short)(y - radius),
|
|
(short)(2*radius), (short)(2*radius),
|
|
0, 23040);
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), color);
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), 2,
|
|
LineSolid, CapRound, JoinRound);
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius), (short)(y - radius),
|
|
(short)(2*radius), (short)(2*radius),
|
|
0, 23040);
|
|
|
|
/* Draw finger holes */
|
|
XSetLineAttributes(MI_DISPLAY(mi), MI_GC(mi), holesize,
|
|
LineSolid, CapRound, JoinRound);
|
|
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius*0.5), (short)(y - radius*0.5),
|
|
(short)(2*radius*0.5), (short)(2*radius*0.5),
|
|
(offset + 960) % 23040, 0);
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius*0.7), (short)(y - radius*0.7),
|
|
(short)(2*radius*0.7), (short)(2*radius*0.7),
|
|
(offset + 1920) % 23040, 0);
|
|
XDrawArc(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
(short)(x - radius*0.7), (short)(y - radius*0.7),
|
|
(short)(2*radius*0.7), (short)(2*radius*0.7),
|
|
offset % 23040, 0);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* Public Functions *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
|
|
void
|
|
release_juggle(ModeInfo * mi)
|
|
{
|
|
if (juggles != NULL) {
|
|
int screen;
|
|
|
|
for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
|
|
free_juggle(&juggles[screen]);
|
|
free(juggles);
|
|
juggles = (jugglestruct *) NULL;
|
|
}
|
|
if (mode_font!=None) {
|
|
XFreeFontInfo(NULL,mode_font,1);
|
|
mode_font = None;
|
|
}
|
|
}
|
|
|
|
/* FIXME: refill_juggle currently just appends new throws to the
|
|
* programme. This is fine if the programme is empty, but if there
|
|
* are still some trajectories left then it really should take these
|
|
* into account */
|
|
|
|
static void
|
|
refill_juggle(ModeInfo * mi)
|
|
{
|
|
jugglestruct *sp = NULL;
|
|
int i;
|
|
|
|
if (juggles == NULL)
|
|
return;
|
|
sp = &juggles[MI_SCREEN(mi)];
|
|
|
|
/* generate pattern */
|
|
if (pattern == NULL) {
|
|
|
|
#define MAXPAT 10
|
|
#define MAXREPEAT 300
|
|
#define CHANGE_BIAS 8 /* larger makes num_ball changes less likely */
|
|
#define POSITION_BIAS 20 /* larger makes hand movements less likely */
|
|
|
|
int count = 0;
|
|
while (count < MI_CYCLES(mi)) {
|
|
char buf[MAXPAT * 3 + 3], *b = buf;
|
|
int maxseen = 0;
|
|
int l = NRAND(MAXPAT) + 1;
|
|
int t = NRAND(MIN(MAXREPEAT, (MI_CYCLES(mi) - count))) + 1;
|
|
|
|
{ /* vary number of balls */
|
|
int new_balls = sp->num_balls;
|
|
int change;
|
|
|
|
if (new_balls == 2) /* Do not juggle 2 that often */
|
|
change = NRAND(2 + CHANGE_BIAS / 4);
|
|
else
|
|
change = NRAND(2 + CHANGE_BIAS);
|
|
switch (change) {
|
|
case 0:
|
|
new_balls++;
|
|
break;
|
|
case 1:
|
|
new_balls--;
|
|
break;
|
|
default:
|
|
break; /* NO-OP */
|
|
}
|
|
if (new_balls < patternindex.minballs) {
|
|
new_balls += 2;
|
|
}
|
|
if (new_balls > patternindex.maxballs) {
|
|
new_balls -= 2;
|
|
}
|
|
if (new_balls < sp->num_balls) {
|
|
if (!program(mi, "[*]", NULL, 1)) /* lose ball */
|
|
return;
|
|
}
|
|
sp->num_balls = new_balls;
|
|
}
|
|
|
|
count += t;
|
|
if (NRAND(2) && patternindex.index[sp->num_balls].number) {
|
|
/* Pick from PortFolio */
|
|
int p = patternindex.index[sp->num_balls].start +
|
|
NRAND(patternindex.index[sp->num_balls].number);
|
|
if (!program(mi, portfolio[p].pattern, portfolio[p].name, t))
|
|
return;
|
|
} else {
|
|
/* Invent a new pattern */
|
|
*b++='[';
|
|
for(i = 0; i < l; i++){
|
|
int n, m;
|
|
do { /* Triangular Distribution => high values more likely */
|
|
m = NRAND(sp->num_balls + 1);
|
|
n = NRAND(sp->num_balls + 1);
|
|
} while(m >= n);
|
|
if (n == sp->num_balls) {
|
|
maxseen = 1;
|
|
}
|
|
switch(NRAND(5 + POSITION_BIAS)){
|
|
case 0: /* Outside throw */
|
|
*b++ = '+'; break;
|
|
case 1: /* Cross throw */
|
|
*b++ = '='; break;
|
|
case 2: /* Cross catch */
|
|
*b++ = '&'; break;
|
|
case 3: /* Cross throw and catch */
|
|
*b++ = 'x'; break;
|
|
case 4: /* Bounce */
|
|
*b++ = '_'; break;
|
|
default:
|
|
break; /* Inside throw (default) */
|
|
}
|
|
|
|
*b++ = n + '0';
|
|
*b++ = ' ';
|
|
}
|
|
*b++ = ']';
|
|
*b = '\0';
|
|
if (maxseen) {
|
|
if (!program(mi, buf, NULL, t))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else { /* pattern supplied in height or 'a' notation */
|
|
if (!program(mi, pattern, NULL, MI_CYCLES(mi)))
|
|
return;
|
|
}
|
|
|
|
adam(sp);
|
|
|
|
name(sp);
|
|
|
|
if (!part(sp))
|
|
return;
|
|
|
|
lob(mi);
|
|
|
|
clap(sp);
|
|
|
|
positions(sp);
|
|
|
|
if (!projectile(sp)) {
|
|
free_juggle(sp);
|
|
return;
|
|
}
|
|
|
|
hands(sp);
|
|
#ifdef DEBUG
|
|
if(MI_IS_DEBUG(mi)) dump(sp);
|
|
#endif
|
|
}
|
|
void
|
|
change_juggle(ModeInfo * mi)
|
|
{
|
|
jugglestruct *sp = NULL;
|
|
Trajectory *t;
|
|
|
|
if (juggles == NULL)
|
|
return;
|
|
sp = &juggles[MI_SCREEN(mi)];
|
|
|
|
/* Strip pending trajectories */
|
|
for (t = sp->head->next; t != sp->head; t = t->next) {
|
|
if(t->start > sp->time || t->finish < sp->time) {
|
|
Trajectory *n = t;
|
|
t=t->prev;
|
|
trajectory_destroy(n);
|
|
}
|
|
}
|
|
|
|
/* Pick the current object theme */
|
|
sp->objtypes = choose_object();
|
|
|
|
refill_juggle(mi);
|
|
|
|
/* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we
|
|
don't all those special effects. */
|
|
XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
|
|
|
|
show_figure(mi, MI_WHITE_PIXEL(mi), True);
|
|
|
|
}
|
|
|
|
#ifdef STANDALONE
|
|
/* Used by xscreensaver. xlock just uses init_juggle */
|
|
void
|
|
reshape_juggle(ModeInfo * mi, int width, int height)
|
|
{
|
|
init_juggle(mi);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
init_juggle(ModeInfo * mi)
|
|
{
|
|
jugglestruct *sp;
|
|
int i;
|
|
|
|
if (only && *only && strcmp(only, " ")) {
|
|
balls = clubs = torches = knives = rings = bballs = False;
|
|
if (!strcasecmp (only, "balls")) balls = True;
|
|
else if (!strcasecmp (only, "clubs")) clubs = True;
|
|
else if (!strcasecmp (only, "torches")) torches = True;
|
|
else if (!strcasecmp (only, "knives")) knives = True;
|
|
else if (!strcasecmp (only, "rings")) rings = True;
|
|
else if (!strcasecmp (only, "bballs")) bballs = True;
|
|
else {
|
|
(void) fprintf (stderr,
|
|
"Juggle: -only must be one of: balls, clubs, torches, knives,\n"
|
|
"\t rings, or bballs (not \"%s\")\n", only);
|
|
#ifdef STANDALONE /* xlock mustn't exit merely because of a bad argument */
|
|
exit (1);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (pattern != NULL && *pattern == '.') {
|
|
pattern = NULL;
|
|
}
|
|
if (pattern == NULL && patternindex.maxballs == 0) {
|
|
/* pattern list needs indexing */
|
|
int nelements = XtNumber(portfolio);
|
|
int numpat = 0;
|
|
|
|
/* sort according to number of balls */
|
|
qsort((void*)portfolio, nelements,
|
|
sizeof(portfolio[1]), compare_num_balls);
|
|
|
|
/* last pattern has most balls */
|
|
patternindex.maxballs = get_num_balls(portfolio[nelements - 1].pattern);
|
|
/* run through sorted list, indexing start of each group
|
|
and number in group */
|
|
patternindex.maxballs = 1;
|
|
for (i = 0; i < nelements; i++) {
|
|
int b = get_num_balls(portfolio[i].pattern);
|
|
if (b > patternindex.maxballs) {
|
|
patternindex.index[patternindex.maxballs].number = numpat;
|
|
if(numpat == 0) patternindex.minballs = b;
|
|
patternindex.maxballs = b;
|
|
numpat = 1;
|
|
patternindex.index[patternindex.maxballs].start = i;
|
|
} else {
|
|
numpat++;
|
|
}
|
|
}
|
|
patternindex.index[patternindex.maxballs].number = numpat;
|
|
}
|
|
|
|
/* Clean up the Screen. Don't use MI_CLEARWINDOW(mi), since we may
|
|
only be resizing and then we won't all those special effects. */
|
|
XClearWindow(MI_DISPLAY(mi), MI_WINDOW(mi));
|
|
|
|
if (juggles == NULL) { /* First-time initialisation */
|
|
|
|
/* allocate jugglestruct */
|
|
if ((juggles =
|
|
(jugglestruct *)calloc(MI_NUM_SCREENS(mi),
|
|
sizeof (jugglestruct))) == NULL) {
|
|
release_juggle(mi);
|
|
return;
|
|
}
|
|
|
|
sp = &juggles[MI_SCREEN(mi)];
|
|
|
|
sp->count = ABS(MI_COUNT(mi));
|
|
if (sp->count == 0)
|
|
sp->count = 200;
|
|
|
|
/* record start time */
|
|
sp->begintime = time(NULL);
|
|
if(patternindex.maxballs > 0) {
|
|
sp->num_balls = patternindex.minballs +
|
|
NRAND(patternindex.maxballs - patternindex.minballs);
|
|
}
|
|
|
|
show_figure(mi, MI_WHITE_PIXEL(mi), True); /* Draw figure. Also discovers
|
|
information about the juggler's
|
|
proportions */
|
|
|
|
/* "7" should be about three times the height of the juggler's
|
|
shoulders */
|
|
sp->Gr = -GRAVITY(3 * sp->arm[0][RIGHT][SHOULDER].y,
|
|
7 * THROW_CATCH_INTERVAL);
|
|
|
|
if(!balls && !clubs && !torches && !knives && !rings && !bballs)
|
|
balls = True; /* Have to juggle something! */
|
|
|
|
/* create circular trajectory list */
|
|
ADD_ELEMENT(Trajectory, sp->head, sp->head);
|
|
if(sp->head == NULL){
|
|
free_juggle(sp);
|
|
return;
|
|
}
|
|
|
|
/* create circular object list */
|
|
ADD_ELEMENT(Object, sp->objects, sp->objects);
|
|
if(sp->objects == NULL){
|
|
free_juggle(sp);
|
|
return;
|
|
}
|
|
|
|
/* create circular wander list */
|
|
ADD_ELEMENT(Wander, sp->wander, sp->wander);
|
|
if(sp->wander == NULL){
|
|
free_juggle(sp);
|
|
return;
|
|
}
|
|
(void)wander(sp, 0); /* Initialize wander */
|
|
|
|
sp->pattern = strdup(""); /* Initialise saved pattern with
|
|
free-able memory */
|
|
|
|
/* Set up programme */
|
|
change_juggle(mi);
|
|
|
|
}
|
|
|
|
/* Only put things here that won't interrupt the programme during
|
|
a window resize */
|
|
|
|
sp = &juggles[MI_SCREEN(mi)];
|
|
|
|
/* Use MIN so that users can resize in interesting ways, eg
|
|
narrow windows for tall patterns, etc */
|
|
sp->scale = MIN(MI_HEIGHT(mi)/480.0, MI_WIDTH(mi)/160.0);
|
|
|
|
if(describe && mode_font == None) { /* Check to see if there's room to describe patterns. */
|
|
mode_font = (XFontStruct *) XQueryFont(MI_DISPLAY(mi), XGContextFromGC(MI_GC(mi)));
|
|
}
|
|
}
|
|
|
|
void
|
|
draw_juggle(ModeInfo * mi)
|
|
{
|
|
Trajectory *traj = NULL;
|
|
Object *o = NULL;
|
|
unsigned long future = 0;
|
|
jugglestruct *sp = NULL;
|
|
char *pattern = NULL;
|
|
double cx;
|
|
|
|
if (juggles == NULL)
|
|
return;
|
|
sp = &juggles[MI_SCREEN(mi)];
|
|
|
|
MI_IS_DRAWN(mi) = True;
|
|
|
|
/* Update timer */
|
|
#ifndef WIN32
|
|
if (real) {
|
|
struct timeval tv;
|
|
(void)gettimeofday(&tv, NULL);
|
|
sp->time = (int) ((tv.tv_sec - sp->begintime)*1000 + tv.tv_usec/1000);
|
|
} else
|
|
#endif
|
|
{
|
|
sp->time += MI_DELAY(mi) / 1000;
|
|
}
|
|
|
|
/* First pass: Move arms and strip out expired elements */
|
|
for (traj = sp->head->next; traj != sp->head; traj = traj->next) {
|
|
if (traj->status != PREDICTOR) {
|
|
/* Skip any elements that need further processing */
|
|
/* We could remove them, but there shoudn't be many and they
|
|
would be needed if we ever got the pattern refiller
|
|
working */
|
|
continue;
|
|
}
|
|
if (traj->start > future) { /* Lookahead to the end of the show */
|
|
future = traj->start;
|
|
}
|
|
if (sp->time < traj->start) { /* early */
|
|
continue;
|
|
} else if (sp->time < traj->finish) { /* working */
|
|
|
|
/* Look for pattern name */
|
|
if(traj->pattern != NULL) {
|
|
pattern=traj->pattern;
|
|
}
|
|
|
|
if (traj->type == Empty || traj->type == Full) {
|
|
/* Only interested in hands on this pass */
|
|
double angle = traj->angle + traj->spin * (sp->time - traj->start);
|
|
double xd = 0, yd = 0;
|
|
DXPoint p;
|
|
|
|
/* Find the catching offset */
|
|
if(traj->object != NULL) {
|
|
if(ObjectDefs[traj->object->type].handle > 0) {
|
|
/* Handles Need to be oriented */
|
|
xd = ObjectDefs[traj->object->type].handle *
|
|
PERSPEC * sin(angle);
|
|
yd = ObjectDefs[traj->object->type].handle *
|
|
cos(angle);
|
|
} else {
|
|
/* Balls are always caught at the bottom */
|
|
xd = 0;
|
|
yd = -4;
|
|
}
|
|
}
|
|
p.x = (CUBIC(traj->xp, sp->time) - xd);
|
|
p.y = (CUBIC(traj->yp, sp->time) + yd);
|
|
reach_arm(mi, traj->hand, &p);
|
|
|
|
/* Store updated hand position */
|
|
traj->x = p.x + xd;
|
|
traj->y = p.y - yd;
|
|
}
|
|
if (traj->type == Ball || traj->type == Full) {
|
|
/* Only interested in objects on this pass */
|
|
double x, y;
|
|
Trace *s;
|
|
|
|
if(traj->type == Full) {
|
|
/* Adjusted these in the first pass */
|
|
x = traj->x;
|
|
y = traj->y;
|
|
} else {
|
|
x = CUBIC(traj->xp, sp->time);
|
|
y = CUBIC(traj->yp, sp->time);
|
|
}
|
|
|
|
ADD_ELEMENT(Trace, s, traj->object->trace->prev);
|
|
s->x = x;
|
|
s->y = y;
|
|
s->angle = traj->angle + traj->spin * (sp->time - traj->start);
|
|
s->divisions = traj->divisions;
|
|
traj->object->tracelen++;
|
|
traj->object->active = True;
|
|
}
|
|
} else { /* expired */
|
|
Trajectory *n = traj;
|
|
traj=traj->prev;
|
|
trajectory_destroy(n);
|
|
}
|
|
}
|
|
|
|
/* Erase end of trails */
|
|
for (o = sp->objects->next; o != sp->objects; o = o->next) {
|
|
Trace *s;
|
|
for (s = o->trace->next;
|
|
o->trace->next != o->trace &&
|
|
(o->count == 0 || o->tracelen > o->tail);
|
|
s = o->trace->next) {
|
|
ObjectDefs[o->type].draw(mi, MI_BLACK_PIXEL(mi), s);
|
|
REMOVE(s);
|
|
o->tracelen--;
|
|
if(o->count <= 0 && o->tracelen <= 0) {
|
|
/* Object no longer in use and trail gone */
|
|
Object *n = o;
|
|
o = o->prev;
|
|
object_destroy(n);
|
|
}
|
|
if(o->count <= 0) break; /* Allow loop for catch-up, but not clean-up */
|
|
}
|
|
}
|
|
|
|
show_arms(mi, MI_BLACK_PIXEL(mi));
|
|
cx = wander(sp, sp->time);
|
|
/* Reduce flicker by only permitting movements of more than a pixel */
|
|
if(fabs((sp->cx - cx))*sp->scale >= 2.0 ) {
|
|
show_figure(mi, MI_BLACK_PIXEL(mi), False);
|
|
sp->cx = cx;
|
|
}
|
|
|
|
show_figure(mi, MI_WHITE_PIXEL(mi), False);
|
|
|
|
show_arms(mi, MI_WHITE_PIXEL(mi));
|
|
|
|
/* Draw Objects */
|
|
for (o = sp->objects->next; o != sp->objects; o = o->next) {
|
|
if(o->active) {
|
|
ObjectDefs[o->type].draw(mi,MI_PIXEL(mi, o->color), o->trace->prev);
|
|
o->active = False;
|
|
}
|
|
}
|
|
|
|
|
|
/* Save pattern name so we can erase it when it changes */
|
|
if(pattern != NULL && strcmp(sp->pattern, pattern) != 0 ) {
|
|
/* Erase old name */
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_BLACK_PIXEL(mi));
|
|
XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
0, 20, sp->pattern, strlen(sp->pattern));
|
|
free(sp->pattern);
|
|
sp->pattern = strdup(pattern);
|
|
|
|
if (MI_IS_VERBOSE(mi)) {
|
|
(void) fprintf(stderr, "Juggle[%d]: Running: %s\n",
|
|
MI_SCREEN(mi), sp->pattern);
|
|
}
|
|
}
|
|
if(mode_font != None &&
|
|
XTextWidth(mode_font, sp->pattern, strlen(sp->pattern)) < MI_WIDTH(mi)) {
|
|
/* Redraw once a cycle, in case it's obscured or it changed */
|
|
XSetForeground(MI_DISPLAY(mi), MI_GC(mi), MI_WHITE_PIXEL(mi));
|
|
XDrawString(MI_DISPLAY(mi), MI_WINDOW(mi), MI_GC(mi),
|
|
0, 20, sp->pattern, strlen(sp->pattern));
|
|
}
|
|
|
|
#ifdef MEMTEST
|
|
if((int)(sp->time/10) % 1000 == 0)
|
|
(void) fprintf(stderr, "sbrk: %d\n", (int)sbrk(0));
|
|
#endif
|
|
|
|
if (future < sp->time + 100 * THROW_CATCH_INTERVAL) {
|
|
refill_juggle(mi);
|
|
} else if (sp->time > 1<<30) { /* Hard Reset before the clock wraps */
|
|
release_juggle(mi);
|
|
init_juggle(mi);
|
|
}
|
|
}
|
|
|
|
#endif /* MODE_juggle */
|