//
import render.*;
public class Polly extends Actor
{
//----- DATA DEFINING ALL THE KEY ACTIONS
// THE BASE UNANIMATED SHAPE
static double shape[] = { 0,1,0, 0,0,.5, 0,1,1, 1,1,0, 1,0,.5, 1,1,1 };
// INITIALIZE THE ACTIONS
public static final int NACTIONS = 12;
static String name [] = new String[NACTIONS]; // NAME OF ACTION
static double motion[][] = new double[NACTIONS][]; // KEY FRAME MOTION
static double rate [] = new double[NACTIONS]; // BEATS PER SECOND OF ACTION
static double travel[] = new double[NACTIONS]; // DISTANCE TRAVELED PER BEAT
static int nActions = 0;
static int addAction(String s, double data[], double r, double t) {
name [nActions] = s;
motion[nActions] = data;
rate [nActions] = r;
travel[nActions] = t;
return nActions++;
}
static int IDLE,SCAMPER,SWAGGER,BROADJUMP,PROWL,LUMBER,DEJECTED,NO,YES,HOTFEET,SPRINT,HOP;
static boolean addActions() {
double idle[] = {
.01,1.0,.00, 0.,0.,0.5, .02,1.00,1.01, 1.02,1.,.03, 1.,0.,.5, 1.01,1.00,1.03,
.03,1.0,.02, 0.,0.,0.5, .00,1.03,1.03, 1.01,1.,.02, 1.,0.,.5, 1.00,1.03,1.00,
.01,1.0,.01, 0.,0.,0.5, .01,1.00,1.00, 1.03,1.,.00, 1.,0.,.5, 1.03,1.00,1.01,
.02,1.0,.01, 0.,0.,0.5, .00,0.97,1.02, 1.00,1.,.01, 1.,0.,.5, 1.02,0.97,1.02, };
IDLE = addAction("idle", idle, 4, 0);
double scamper[] = {
0.,1.03,0.0, 0.1,0.0,1.0, 0.,1.03,1.0, 1.,1.17,0.0, 0.9,0.0,0.0, 1.,1.17,1.0,
0.,1.17,0.0, 0.0,0.2,0.5, 0.,1.17,1.0, 1.,1.03,0.0, 0.9,0.0,0.5, 1.,1.03,1.0,
0.,1.17,0.0, 0.1,0.0,0.0, 0.,1.17,1.0, 1.,1.03,0.0, 0.9,0.0,1.0, 1.,1.03,1.0,
0.,1.03,0.0, 0.1,0.0,0.5, 0.,1.03,1.0, 1.,1.17,0.0, 1.0,0.2,0.5, 1.,1.17,1.0, };
SCAMPER = addAction("scamper", scamper, 8, .5);
double swagger[] = {
0.0,1.0,0.0, 0.1,0.0,1.0, 0.0,1.0,1.0, 1.0,1.0,-.1, 0.9,0.0,0.0, 1.0,0.9,1.0,
0.2,1.5,0.0, 0.0,0.5,0.3, 0.2,1.5,1.0, 1.2,1.0,0.0, 0.9,0.0,0.3, 1.2,1.0,1.0,
0.0,1.0,-.1, 0.1,0.0,0.0, 0.0,1.0,0.9, 1.0,1.0,0.0, 0.9,0.0,1.0, 1.0,1.0,1.0,
-.2,1.0,0.0, 0.1,0.0,0.3, -.2,1.0,1.0, 0.8,1.5,0.0, 1.0,0.5,0.3, 0.8,1.5,1.0, };
SWAGGER = addAction("swagger", swagger, 6, .5);
double broadjump[] = {
0.0,0.4,0.1, 0.0,0.0,0.9, 0.0,0.7,1.0, 1.0,0.4,0.1, 1.0,0.0,0.9, 1.0,0.7,1.0,
0.0,1.9,0.0, 0.1,0.7,0.5, 0.0,1.8,1.0, 1.0,1.9,0.0, 0.9,0.7,0.5, 1.0,1.8,1.0,
0.0,1.9,0.0, 0.1,0.5,0.0, 0.0,1.6,1.0, 1.0,1.9,0.0, 0.9,0.5,0.0, 1.0,1.6,1.0,
0.0,1.0,0.0, 0.0,0.0,0.3, 0.0,1.0,1.0, 1.0,1.0,0.0, 1.0,0.0,0.3, 1.0,1.0,1.0, };
BROADJUMP = addAction("broadjump", broadjump, 7, .45);
double prowl[] = {
0.0,0.6,-.1, 0.2,0.0,0.9, 0.0,1.0,1.0, 1.0,0.8,-.1, 0.8,0.0,0.0, 1.0,1.0,0.8,
0.1,1.0,0.0, 0.0,0.3,0.1, 0.1,0.8,1.0, 1.1,1.0,0.0, 0.8,0.0,.45, 1.1,0.8,1.0,
0.0,0.8,-.1, 0.2,0.0,0.0, 0.0,1.0,0.8, 1.0,0.6,-.1, 0.8,0.0,0.9, 1.0,1.0,1.0,
-.1,1.0,0.0, 0.2,0.0,.45, -.1,0.8,1.0, 0.9,1.0,0.0, 1.0,0.3,0.1, 0.9,0.8,1.0, };
PROWL = addAction("prowl", prowl, 2, .45);
double lumber[] = {
0.0,1.0,0.0, 0.1,0.0,1.0, 0.0,1.0,1.0, 1.0,1.0,-.1, 0.9,0.0,0.0, 1.0,0.9,1.0,
0.0,1.2,0.0, 0.0,0.1,0.5, 0.0,1.2,1.0, 1.0,1.0,0.0, 0.9,0.0,0.5, 1.0,1.0,1.0,
0.0,1.0,-.1, 0.1,0.0,0.0, 0.0,1.0,0.9, 1.0,1.0,0.0, 0.9,0.0,1.0, 1.0,1.0,1.0,
0.0,1.0,0.0, 0.1,0.0,0.5, 0.0,1.0,1.0, 1.0,1.2,0.0, 1.0,0.1,0.5, 1.0,1.2,1.0, };
LUMBER = addAction("lumber", lumber, 2, .5);
double dejected[] = {
0.0,0.5,0.0, 0.1,0.0,0.8, 0.,1.00,1.0, 1.0,0.7,-.1, 0.9,0.0,0.2, 1.,0.95,1.0,
0.0,0.8,0.0, 0.0,0.1,0.5, 0.,1.05,1.0, 1.0,0.7,0.0, 0.9,0.0,0.5, 1.,1.00,1.0,
0.0,0.7,-.1, 0.1,0.0,0.2, 0.,1.00,0.9, 1.0,0.5,0.0, 0.9,0.0,0.8, 1.,1.00,1.0,
0.0,0.7,0.0, 0.1,0.0,0.5, 0.,0.95,1.0, 1.0,0.8,0.0, 1.0,0.1,0.5, 1.,1.05,1.0, };
DEJECTED = addAction("dejected", dejected, 2, .3);
double no[] = {
0.1,0.9,-.1, 0.0,0.0,0.5, -.1,1.1,0.9, 1.1,0.9,0.1, 1.0,0.0,0.5, 0.9,1.1,1.1,
0.1,0.9,-.1, 0.0,0.0,0.5, -.1,1.1,0.9, 1.1,0.9,0.1, 1.0,0.0,0.5, 0.9,1.1,1.1,
-.1,0.9,0.1, 0.0,0.0,0.5, 0.1,1.1,1.1, 0.9,0.9,-.1, 1.0,0.0,0.5, 1.1,1.1,0.9,
-.1,0.9,0.1, 0.0,0.0,0.5, 0.1,1.1,1.1, 0.9,0.9,-.1, 1.0,0.0,0.5, 1.1,1.1,0.9, };
NO = addAction("no", no, 10, 0);
double yes[] = {
0.,0.90,-.1, 0.0,0.0,0.5, 0.,1.05,1.0, 1.,0.90,-.1, 1.0,0.0,0.5, 1.,1.05,1.0,
0.,1.15,-.1, 0.0,0.0,0.5, 0.,0.93,1.0, 1.,1.15,-.1, 1.0,0.0,0.5, 1.,0.93,1.0,
0.,1.15,-.1, 0.0,0.0,0.5, 0.,0.93,1.0, 1.,1.15,-.1, 1.0,0.0,0.5, 1.,0.93,1.0,
0.,0.90,-.1, 0.0,0.0,0.5, 0.,1.05,1.0, 1.,0.90,-.1, 1.0,0.0,0.5, 1.,1.05,1.0, };
YES = addAction("yes", yes, 12, 0);
double hotfeet[] = {
0.0,1.2,0.1, 0.0,0.3,0.5, 0.0,1.2,1.0, 1.0,1.2,0.0, 1.0,0.3,0.5, 1.0,1.2,1.0,
0.0,1.3,0.0, 0.2,0.5,0.5, 0.0,1.3,1.0, 1.0,1.1,0.0, 1.0,-.1,0.5, 1.0,1.1,1.0,
0.0,1.2,0.0, 0.0,0.3,0.5, 0.0,1.2,1.0, 1.0,1.2,0.1, 1.0,0.3,0.5, 1.0,1.2,1.0,
0.0,1.1,0.0, 0.0,-.1,0.5, 0.0,1.1,1.0, 1.0,1.3,0.0, 0.8,0.5,0.5, 1.0,1.3,1.0, };
HOTFEET = addAction("hotfeet", hotfeet, 10, 0);
double sprint[] = {
0.0,1.1,0.1, 0.2,0.3,1.0, 0.0,1.2,1.0, 1.0,1.1,0.0, 0.8,0.3,0.2, 1.0,1.2,1.0,
0.0,1.0,0.0, -.1,0.5,0.6, 0.0,1.1,1.0, 1.0,1.2,0.0, 0.8,-.1,0.6, 1.0,1.3,1.0,
0.0,1.1,0.0, 0.2,0.3,0.2, 0.0,1.2,1.0, 1.0,1.1,0.1, 0.8,0.3,1.0, 1.0,1.2,1.0,
0.0,1.2,0.0, 0.2,-.1,0.6, 0.0,1.3,1.0, 1.0,1.0,0.0, 1.1,0.5,0.6, 1.0,1.1,1.0, };
SPRINT = addAction("sprint", sprint, 9, .7);
double hop[] = {
0.7,1.3,0.0, 0.2,0.4,0.5, 0.7,1.3,1.0, 1.4,1.0,0.0, 1.0,0.0,0.5, 1.4,1.0,1.0,
0.7,1.4,0.0, 0.2,0.5,0.5, 0.7,1.4,1.0, 1.5,0.9,0.0, 1.0,0.0,0.5, 1.5,1.0,0.9,
0.7,1.5,0.0, 0.2,0.6,0.5, 0.7,1.5,1.0, 1.6,0.8,0.0, 1.0,0.2,0.5, 1.6,1.0,0.8,
0.7,1.4,0.0, 0.2,0.5,0.5, 0.7,1.4,1.0, 1.5,0.9,0.0, 1.0,0.0,0.5, 1.5,1.0,0.9, };
HOP = addAction("hop", hop, 10, 0);
return true;
}
static boolean addedActions = addActions();
// HOW THE VERTICES OF THE POLYHEDRON ARE CONNECTED BY FACES
static int faces[][]={{0,1,2},{3,4,5},{6,7,8,9},{10,11,12,13},{14,15,16,17}};
static int map[] = { 0,1,2, 5,4,3, 1,0,3,4, 2, 1, 4, 5, 0, 2, 5, 3 };
static Material red = (new Material()).setColor(1,0,0, 1,1,1,10, .2,0,0);
static public void clearPollys() { clearActors(); }
//----- STUFF THAT'S UNIQUE TO EACH INDIVIDUAL POLLY
// CONSTRUCTOR
public Polly() {
super();
setMaterial(red);
body = add();
body.faces = faces;
body.vertices = new double[18 + nb][6];
body.matrix.translate(-.5,0,-.5);
Geometry shadow = add();
Material m = (new Material()).setColor(0,0,0).setTransparency(.75);
m.tableMode = false;
shadow.setMaterial(m);
shadow.faces = body.faces;
shadow.vertices = body.vertices;
shadow.matrix.translate(-.5,.02,-.5);
shadow.matrix.scale(1.1,.02,1.1);
}
// ALLOW APPLICATION TO SET VARIOUS PARAMETERS
public Polly setNodding(double t) { noddingAngle_target = t; return this; }
public Polly setTurning(double t) { turningAngle_target = t; return this; }
public Polly setColor(double r, double g, double b) {
Material m = (new Material()).setColor(r,g,b, .8,.8,.8,8, .2*r,.2*g,.2*b);
m.tableMode = false;
child[0].setMaterial(m);
return this;
}
double sqrtT = 1;
public Actor setThrottle(double t) {
super.setThrottle(t);
sqrtT = Math.sqrt(throttle);
return (Actor)this;
}
// ALLOW APPLICATION TO CHOOSE THE NEXT ACTION
public void setAction(String s) {
for (int i = 0 ; i < nActions ; i++)
if (s.equals(name[i])) {
setAction(i);
break;
}
}
public void setAction(int action) {
if (action >= 0 && action < nActions && action != action1) {
action0 = action1;
action1 = action;
transition = 0;
}
}
// ANIMATE ONE FRAME
double animatedShape[] = new double[18];
public void animate(double time) {
super.animate(time);
// SMOOTH OUT TIME-VARYING PARAMETERS
noddingAngle = smooth(noddingAngle, noddingAngle_target);
turningAngle = smooth(turningAngle, turningAngle_target);
// SET POLLY'S SHAPE ACCORDING TO CURRENT ACTION AND KEY POSES
transition = Math.min(1, transition + elapsed);
beat = (beat + sqrtT*elapsed*lerp(transition,rate[action0],rate[action1])) % 4;
int key = (int)beat;
double t = sizeX / sizeY;
for (int i = 0 ; i < animatedShape.length ; i++) {
animatedShape[i] = coord(transition,beat%1,action0,action1,key,(key+1)%4,i);
if (i % 3 == 1 && t != 1)
animatedShape[i] = lerp(t, shape[i % 18], animatedShape[i]);
}
for (int v = 0 ; v < 18 ; v++)
for (int j = 0 ; j < 3 ; j++)
body.vertices[v][j] = animatedShape[3*map[v] + j];
for (int v = 18 ; v < body.vertices.length ; v++) {
int src[] = blendSrc[v - 18];
double wgt[] = blendWgt[v - 18];
for (int j = 0 ; j < 3 ; j++) {
body.vertices[v][j] = 0;
for (int i = 0 ; i < src.length ; i++)
body.vertices[v][j] += wgt[i] * animatedShape[3 * src[i] + j];
}
}
double nodAngle = noddingAngle, turnAngle = turningAngle;
// SET GAZE
if (gw != 0) {
findHead(head); // FIND LOCAL HEAD POSITION
double dx = gx - x, dz = gz - z; // COMPUTE TURN AND NOD ANGLES
double turn = Math.atan2(dx, dz) - theta;
double nod = Math.atan2(gy - head[1], Math.sqrt(dx*dx + dz*dz));
turn = ((turn+Math.PI)%(2*Math.PI)) - Math.PI; // DON'T GAZE AT THINGS BEHIND ME
double f = Math.abs(turn) / (Math.PI/4);
if (f < 1 && gw < 0) // IF GAZE IS NEGATIVE (AVOIDING)
turn = Math.pow(10,1-f) * turn; // TRY NOT TO LOOK STRAIGHT AHEAD
double w = f<1 ? 1 : f>2 ? 0 : 2-f;
double tw = Math.abs(gw) * w;
turnAngle = lerp(tw, turnAngle, gw < 0 ? -turn : turn);
double nw = gw < 0 ? 0 : gw * w;
nodAngle += nw * nod;
}
if (nodAngle != 0 || turnAngle != 0) {
double v[][] = body.vertices; // NOD AND TURN THE HEAD
for (int i = 0 ; i < v.length ; i++)
if (! isFoot(i)) { // IF THIS IS NOT A FOOT VERTEX
for (int j = 0 ; j < 3 ; j++) v[i][j] -= head[j];
Vec.rotate(v[i], 0, nodAngle); // FIRST NOD
Vec.rotate(v[i], 1, turnAngle); // THEN TURN
for (int j = 0 ; j < 3 ; j++) v[i][j] += head[j];
}
}
child[0].computePolyhedronNormals();
}
double head[] = {0,0,0};
void findHead(double h[]) {
h[0] = h[1] = h[2] = 0;
double v[][] = body.vertices;
for (int i = 0 ; i < v.length ; i++)
if (! isFoot(i))
for (int j = 0 ; j < 3 ; j++)
h[j] += v[i][j];
int count = 0;
for (int i = 0 ; i < v.length ; i++) if (! isFoot(i)) count++;
for (int j = 0 ; j < 3 ; j++) h[j] /= count;
}
// IS THIS A FOOT VERTEX?
boolean isFoot(int i) { return map[i] == 1 || map[i] == 4; }
// COMPUTE HOW FAR FORWARD POLLY TRAVELS IN A SMALL TIME INTERVAL
public double getTravel(double elapsed) {
return elapsed * lerp(transition, travel[action0], travel[action1])
* lerp(transition, rate [action0], rate [action1]);
}
// GET ONE COORDINATE, INTERPOLATING BETWEEN TWO ACTIONS
double coord(double s,double t,int a0,int a1,int k0,int k1,int i) {
return lerp(sqrtT, coord(t,0,k0,k1,i),
lerp(s, coord(t, a0, k0, k1, i), coord(t, a1, k0, k1, i)) );
}
// GET ONE COORDINATE, INTERPOLATING BETWEEN TWO KEY POSES IN AN ACTION
double coord(double t, int action, int key0,int key1, int i) {
return lerp(t, coord(action, key0, i), coord(action, key1, i));
}
// GET ONE COORDINATE FROM DATA
double coord(int action, int key, int i) {
return motion[action][18*key + i];
}
// INSTANCE DATA
int index, action0 = 0, action1 = 0;
double transition=0, beat=0;
Geometry body;
// EXTRA VERTICES, BLENDED FROM THE ORIGINALS
public void addBlendedVertices(double src[][]) {
int s[] = {0,0,0};
double w[] = {0,0,0};
for (int i = 0 ; i < src.length ; i++) {
s[0] = (int)src[i][0];
s[1] = (int)src[i][1];
s[2] = (int)src[i][2];
w[2] = src[i][3];
w[0] = .5 - .5 * w[2];
w[1] = .5 - .5 * w[2];
addBlendedVertex(3, s, w);
}
}
public void addBlendedVertex(int ib, int src[], double wgt[]) {
blendSrc[nb] = new int[ib];
blendWgt[nb] = new double[ib];
for (int i = 0 ; i < ib ; i++) {
blendSrc[nb][i] = src[i];
blendWgt[nb][i] = wgt[i];
}
nb++;
}
int ib = 0;
int src[] = new int[20];
double wgt[] = new double[20];
void beginBlendedVertex() { ib = 0; }
void endBlendedVertex() { addBlendedVertex(ib, src, wgt); }
void addToBlendedVertex(int s, double w) {
src[ib] = s;
wgt[ib] = w;
ib++;
}
int nb = 0;
int blendSrc[][] = new int[20][];
double blendWgt[][] = new double[20][];
double noddingAngle = 0, turningAngle = 0;
double noddingAngle_target = 0, turningAngle_target = 0;
}