//

import render.*;

public class Pollymorph
{

//----- DATA DEFINING ALL THE KEY ACTIONS

   // BEATS PER SECOND FOR EACH ACTION

   static double rate[] = {4, 6, 6, 6, 2, 2, 2, 10, 12, 8, 9, 6};

   // AN ACTION HAS FOUR KEY POSES.  EACH POSE HAS SIX (X,Y,Z) VERTICES.

   static double actions[][] = {
     { //idle
        0,1,0, 0,0,1., 0,1,1, 1,1,0, 1,0,0., 1,1,1,
        0,1,0, 0,0,.5, 0,1,1, 1,1,0, 1,0,.5, 1,1,1,
        0,1,0, 0,0,0., 0,1,1, 1,1,0, 1,0,1., 1,1,1,
        0,1,0, 0,0,.5, 0,1,1, 1,1,0, 1,0,.5, 1,1,1,
     },
     { //trot
        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,
     },
     { //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,
     },
     { //jump
        0.0,0.7,0.1, 0.0,0.0,1.0, 0.0,0.7,1.0, 1.0,0.7,0.1, 1.0,0.0,1.0, 1.0,0.7,1.0,
        0.0,1.8,0.0, 0.1,0.6,0.5, 0.0,1.8,1.0, 1.0,1.8,0.0, 0.9,0.6,0.5, 1.0,1.8,1.0,
        0.0,1.6,0.0, 0.1,0.4,0.0, 0.0,1.6,1.0, 1.0,1.6,0.0, 0.9,0.4,0.0, 1.0,1.6,1.0,
        0.0,1.0,0.0, 0.0,0.0,0.5, 0.0,1.0,1.0, 1.0,1.0,0.0, 1.0,0.0,0.5, 1.0,1.0,1.0,
     },
     { //prowl
        0.0,0.6,-.1, 0.2,0.0,1.0, 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.2, 0.1,0.8,1.0, 1.1,1.0,0.0, 0.8,0.0,0.5, 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,1.0, 1.0,1.0,1.0,
        -.1,1.0,0.0, 0.2,0.0,0.5, -.1,0.8,1.0, 0.9,1.0,0.0, 1.0,0.3,0.2, 0.9,0.8,1.0,
     },
     { //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,
     },
     { //mope
        0.0,0.5,0.0, 0.1,0.0,1.0, 0.,1.00,1.0, 1.0,0.7,-.1, 0.9,0.0,0.0, 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.0, 0.,1.00,0.9, 1.0,0.5,0.0, 0.9,0.0,1.0, 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,
     },
     { //no
        0.1,0.9,-.1, 0.0,0.0,1.0, -.1,1.1,0.9, 1.1,0.9,0.1, 1.0,0.0,0.0, 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.0, 0.1,1.1,1.1, 0.9,0.9,-.1, 1.0,0.0,1.0, 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,
     },
     { //yes
        0.,0.90,-.1, 0.0,0.0,1.0, 0.,1.05,1.0, 1.,0.90,-.1, 1.0,0.0,0.0, 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.0, 0.,0.93,1.0, 1.,1.15,-.1, 1.0,0.0,1.0, 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,
     },
     { //dance
        0.0,1.2,0.1, 0.0,0.3,1.0, 0.0,1.2,1.0, 1.0,1.2,0.0, 1.0,0.3,0.0, 1.0,1.2,1.0,
        0.1,1.1,0.0, 0.1,0.4,0.5, 0.1,1.1,1.0, 1.1,1.1,0.0, 1.0,0.0,0.5, 1.1,1.1,1.0,
        0.0,1.2,0.0, 0.0,0.3,0.0, 0.0,1.2,1.0, 1.0,1.2,0.1, 1.0,0.3,1.0, 1.0,1.2,1.0,
        -.1,1.1,-.1, 0.0,0.0,0.5, -.1,1.1,0.9, 0.9,1.1,-.1, 0.9,0.4,0.5, 0.9,1.1,0.9,
     },
     { //run
        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.0, 1.0,1.2,1.0,
        0.0,1.0,0.0, -.1,0.5,0.5, 0.0,1.1,1.0, 1.0,1.2,0.0, 0.8,-.1,0.5, 1.0,1.3,1.0,
        0.0,1.1,0.0, 0.2,0.3,0.0, 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.5, 0.0,1.3,1.0, 1.0,1.0,0.0, 1.1,0.5,0.5, 1.0,1.1,1.0,
     },
     { //hop
        0.7,1.4,0.0, 0.2,0.5,1.0, 0.7,1.4,1.0, 1.5,0.9,0.0, 1.0,0.0,0.0, 1.5,1.0,0.9,
        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.0, 0.7,1.4,1.0, 1.5,0.9,0.0, 1.0,0.0,1.0, 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.3,0.5, 1.6,1.0,0.8,
     },
   };

   // CURRENT STATE OF POLLY

   double vertices[][] = new double[6][3];
   double transition=0, beat=0, time=0, x=0, y=0, z=0, theta=0, size=1;
   double speedTarget=0, speed=0;
   int index, action0 = 0, action1 = 0;
   Matrix M = new Matrix();
   double sX = 0, sZ = 0;

   // PUBLIC QUERIES

   public double getDirection() { return direction; }
   public double getPhase() { return Math.PI * beat / 2; }
   public double getSize() { return size; }
   public double getSpeed() { return speed; }

   public double getX() { return x + sX; }
   public double getY() { return y     ; }
   public double getZ() { return z + sZ; }

   public double getX(int v) { return size * (vertices[v][0] - .5); }
   public double getY(int v) { return size *  vertices[v][1]; }
   public double getZ(int v) { return size * (.5 - vertices[v][2]); }

   // PARAMETERS THAT CAN BE SET PUBLICLY

   public void changeTurning(double delta) { turningTarget += delta; }
   public void changeDirection(double delta) { direction += delta; }
   public void changeSpeed(double delta) { speedTarget += delta; }

   public void setAction(int action) {
      if (action >= 0 && action < actions.length && action != action1) {
         action0 = action1;
         action1 = action;
         transition = 0;
      }
   }
   public void setTurning(double Turning) { turningTarget = Turning; }
   public void setDirection(double Direction) { direction = Direction; }
   public void setSize(double Size) { size = Size; }
   public void setSpeed(double S) { speedTarget = S; }
   public void setX(double X) { x = X; }
   public void setY(double Y) { y = Y; }
   public void setZ(double Z) { z = Z; }

   public void setNod(double n, double b) { nodTarget = n; nodBoldnessTarget = b; }
   public void setTilt(double t, double b) { tiltTarget = t; tiltBoldnessTarget = b; }
   public void setTurn(double t, double b) { turnTarget = t; turnBoldnessTarget = b; }
   public void setLift(double t) { liftTarget = t; }

   double gx = 0, gy = 0, gz = 0, gw = 0;

   double nodToObject = 0, turnToObject = 0;

   double turning = 0, turningTarget = 0;
   double direction = 0;

   double nod = 0, nodTarget = 0;
   double tilt = 0, tiltTarget = 0;
   double turn = 0, turnTarget = 0;
   double lift = 0, liftTarget = 0;

   double nodBoldness = 0, nodBoldnessTarget = 0;
   double tiltBoldness = 0, tiltBoldnessTarget = 0;
   double turnBoldness = 0, turnBoldnessTarget = 0;

   public void setGaze(double x,double y,double z,double w) {
      gx = x;
      gy = y;
      gz = z;
      gw = w;
   }

   double leftTwist = 0, rightTwist = 0;

   public double getLeftFootTwist(){ return leftTwist; }
   public double getRightFootTwist(){ return rightTwist; }

   // ANIMATE ONE FRAME

   public void animate(double time) {

      // TRAVEL FORWARD

      if (this.time == 0) this.time = time - 1;
      double elapsedTime = time - this.time;
      this.time = time;

      speed = lerp(elapsedTime / 0.5, speed, speedTarget);

      double travel = elapsedTime * size * speed;
      direction += elapsedTime * turning;
      theta = (direction + 100 * Math.PI) % (2 * Math.PI);
      double sin = Math.sin(theta), cos = Math.cos(theta);
      double tx = travel * sin;
      double tz = travel * cos;
      x += tx;
      z += tz;

      // SET SHAPE ACCORDING TO CURRENT ACTION AND KEY POSES

      transition = Math.min(1, transition + elapsedTime);
      double stepsPerSecond = lerp(transition,rate[action0],rate[action1]);
      beat += stepsPerSecond * elapsedTime;
      int key = (int)(beat % 4);
      for (int v = 0 ; v < vertices.length ; v++)
         for (int i = 0 ; i < 3 ; i++)
            vertices[v][i] = coord(transition, beat%1, action0,action1, key,(key+1)%4, v,i);

      // MAKE THE FEET TRAVEL THE RIGHT AMOUNT

      double stepLength = 2 * speed / stepsPerSecond;

      double LX = (vertices[1][0] -= .5);
      double LZ = vertices[1][2] - .5;
      vertices[1][2] = 0;
      M.identity();
      M.rotateY(-turning * LZ / 2);
      M.translate(-.2 * LZ * stepLength * turning, 0, LZ * (stepLength - 2 * turning * LX));
      M.rotateY(-turning * LZ / 2);
      misc.transform(vertices[1], M);
      vertices[1][0] += .5;
      vertices[1][2] += .5;
      leftTwist = -turning * LZ;

      double RX = (vertices[4][0] -= .5);
      double RZ = vertices[4][2] - .5;
      vertices[4][2] = 0;
      M.identity();
      M.rotateY(-turning * RZ / 2);
      M.translate(-.2 * RZ * stepLength * turning, 0, RZ * (stepLength - 2 * turning * RX));
      M.rotateY(-turning * RZ / 2);
      misc.transform(vertices[4], M);
      vertices[4][0] += .5;
      vertices[4][2] += .5;
      rightTwist = -turning * RZ;

      //---------------- TURNING THE HEAD -----------------

      double v[][] = vertices;

      for (int i = 0 ; i < 3 ; i++)
         head[i] = (v[0][i] + v[2][i] + v[3][i] + v[5][i]) / 4;

      // COMPUTE ANGLES FOR GAZING AT AN OBJECT

      if (gw != 0 || nodToObject != 0 || turnToObject != 0) {   // COMPUTE CENTROID OF HEAD

	 double dx = gx - x, dz = gz - z;                 // COMPUTE TURN AND NOD ANGLES
	 double t = Math.atan2(dx, dz) - theta;
	 t = ((t+3*Math.PI)%(2*Math.PI)) - Math.PI;
	 double n = Math.atan2(gy - size * head[1], Math.sqrt(dx*dx + dz*dz));

         double limit = gw > 0 ? Math.PI/3 : Math.PI/4;
	 double f = Math.abs(t) / limit;               // DON'T GAZE AT THINGS BEHIND ME
	 if (f < 1 && gw < 0)                            // IF GAZE IS NEGATIVE (AVOIDING)
	    t = Math.pow(10,1-f) * t;                // TRY NOT TO LOOK STRAIGHT AHEAD
	 double w = f<1 ? 1 : f>2 ? 0 : 2-f;

         double speed = 0.14;
	 turnToObject = lerp(elapsedTime / speed, turnToObject, (gw < 0 ? t : -t) * w);
	 nodToObject  = lerp(elapsedTime / speed, nodToObject , gw <= 0 ? 0 : n * w);
      }

      // BUILD THE MATRIX TO ANIMATE THE HEAD

      M.identity();
      M.translate(head[0],head[1],head[2]);

         // LIFT OR LOWER THE HEAD

         M.translate(0, .25 * lift, 0);

         // ANGLES TO TURN THE HEAD

         M.translate(0, 0, .7*turnBoldness);
         M.rotateY(.5 * turn);
         M.translate(0, 0,-.7*turnBoldness);

         M.translate(0,-tiltBoldness, 0);
         M.rotateZ(.3 * tilt);
         M.translate(0, tiltBoldness, 0);

         M.translate(0, (nod < 0 ? -.7 : .7) * nodBoldness + .3 * nod, 0);
         M.rotateX(.3 * nod);
         M.translate(0, (nod < 0 ? .7 : -.7) * nodBoldness - .3 * nod, 0);

         // ANGLES TO GAZE AT AN OBJECT

         M.rotateY(turnToObject + .1 * Noise.noise(2 * time));
         M.rotateX(nodToObject  + .1 * Noise.noise(2 * time + 10));

      M.translate(-head[0],-head[1],-head[2]);

      // APPLY TURNS TO HEAD VERTICES

      for (int i = 0 ; i < v.length ; i++)
         if (i != 1 && i != 4)
            misc.transform(v[i], M);

/*
      //---------------- FLIP EXPERIMENTS ------------------

      if (action1 == 11) {
         double t = 1 - (beat % 4) / 4;
         t = misc.gain(t, .95);
         t *= 2 * Math.PI;
         double p[] = {0,0,0};
         for (int i = 0 ; i < v.length ; i++)
         for (int j = 0 ; j < 3 ; j++)
            p[j] += v[i][j] / v.length;
         for (int i = 0 ; i < v.length ; i++) {
	    for (int j = 0 ; j < 3 ; j++) v[i][j] -= p[j];
            Vec.rotate(v[i], 0, t);
	    for (int j = 0 ; j < 3 ; j++) v[i][j] += p[j];
         }
      }
*/ 

      //----------------- TIME-VARY ALL PARAMETERS --------------

      double t = Math.min(.5, 10 * elapsedTime);

      turning = lerp(t, turning, turningTarget);

      nod = lerp(t, nod, nodTarget);
      tilt = lerp(t, tilt, tiltTarget);
      turn = lerp(t, turn, turnTarget);
      lift = lerp(t, lift, liftTarget);

      nodBoldness = lerp(t, nodBoldness, nodBoldnessTarget);
      tiltBoldness = lerp(t, tiltBoldness, tiltBoldnessTarget);
      turnBoldness = lerp(t, turnBoldness, turnBoldnessTarget);
   }

   // GET ONE COORDINATE, INTERPOLATING BETWEEN TWO ACTIONS

   double coord(double s,double t,int a0,int a1,int k0,int k1,int v, int i) {
      return lerp(s, coord(t, a0, k0, k1, v, i), coord(t, a1, k0, k1, v, i));
   }

   // GET ONE COORDINATE, INTERPOLATING BETWEEN TWO KEY POSES IN AN ACTION

   double coord(double t, int action, int key0,int key1, int v,int i) {
      return lerp(t, coord(action, key0, v, i), coord(action, key1, v, i));
   }

   // GET ONE COORDINATE FROM DATA

   double coord(int action, int key, int v, int i) {
      return actions[action][3 * (6*key + v) + i];
   }

   double lerp(double t, double a, double b) { return a + t * (b - a); }

   double head[] = {0,0,0};
}