//

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