//
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; }