//

import render.*;

public class Polly extends Geometry
{

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

   // BEATS PER SECOND FOR EACH ACTION

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

   // HOW MUCH DISTANCE EACH ACTION TRAVELS PER BEAT

   static double travel[] = {0, .5, .5, .45, .45, .5, .3, 0, 0, 0, .7, 0, -.5};

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

   static double actions[][] = {
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //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,
     },
     { //scamper backward
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.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,
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.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,
     },
   };

   // 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() { nPollys = 0; }

//----- STUFF THAT'S UNIQUE TO EACH INDIVIDUAL POLLY

   // CONSTRUCTOR

   public Polly() {

      setMaterial(red);

      Geometry body = add();
      body.faces = faces;
      body.vertices = new double[18][6];
      body.matrix.translate(-.5,0,-.5);
      //Matrix.translate(body.matrix,-.5,0,-.5);

      Geometry shadow = add();
      shadow.setMaterial((new Material()).setColor(0,0,0).setTransparency(.75));
      shadow.faces = body.faces;
      shadow.vertices = body.vertices;
      shadow.matrix.translate(-.5,.03,-.5);
      shadow.matrix.scale(1.1,.02,1.1);
      //Matrix.translate(shadow.matrix,-.5,.03,-.5);
      //Matrix.scale(shadow.matrix,1.1,.02,1.1);

      index = nPollys++;
      setP();
   }

   // ALLOW APPLICATION TO SET VARIOUS PARAMETERS

   public Polly setColor(double r, double g, double b) {
      child[0].setMaterial(
         (new Material()).setColor(r,g,b, 1,1,1,10, .2*r,.2*g,.2*b));
      return this;
   }
   public Polly setSize(double Size) {
      size = Size; setP(); return this;
   }
   public Polly setPosition(double X, double Z) {
      x = X; z = Z; setP(); return this;
   }
   public Polly setDirection(double Theta) {
      theta = Theta; return this;
   }

   void setP() {
      p[index][AX] = p[index][BX] = x;
      p[index][AZ] = p[index][BZ] = z;
      p[index][SIZE] = size;
   }

   // ALLOW APPLICATION TO CHOOSE THE NEXT ACTION

   public void action(int action) {
      if (action >= 0 && action < actions.length && action != action1) {
         action0 = action1;
         action1 = action;
         transition = 0;
      }
   }

   // ANIMATE ONE FRAME

   public void animate(double time) {

      // TRAVEL FORWARD

      if (prevTime == 0) prevTime = time - 1;
      double elapsed = time - prevTime;
      prevTime = time;

      double travel = size * getTravel(elapsed);
      double tz = travel * Math.cos(theta);
      double tx = travel * Math.sin(theta);

      z += tz;
      x += tx;

      setP();
      p[index][BX] = x + tx;
      p[index][BZ] = z + tz;

      // TURN AWAY FROM OBSTACLES

      double R = repulse(x,z,size,index);
      if (R > 0) {
         double Rx = (repulse(x+.01,z,size,index) - R) / .01;
         double Rz = (repulse(x,z+.01,size,index) - R) / .01;
         if (Rx*tx > -Rz*tz)                 // IF FACING WALL
	    theta += Rx*tz < Rz*tx ? R : -R; // TURN MORE AWAY
      }

      // SET POLLY'S SHAPE ACCORDING TO CURRENT ACTION AND KEY POSES

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

      // SET TRANSLATION, ROTATION AND SCALE

      //Matrix.identity(matrix);
      //Matrix.translate(matrix,x,0,z);
      //Matrix.rotateY(matrix,Math.PI + theta);
      //Matrix.scale(matrix,size,size,size);

      matrix.identity();
      matrix.translate(x,0,z);
      matrix.rotateY(Math.PI + theta);
      matrix.scale(size,size,size);
   }

   // 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 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][18*key + 3*map[v] + i];
   }

   // LINEAR INTERPOLATOR

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

   // INSTANCE DATA

   int index, action0 = 0, action1 = 0;
   double transition=0, beat=0, prevTime=0, x=0, z=0, theta=0, size=1;

//----- DEFINING STATIC WALLS IN THE SCENE FOR ALL POLLYS TO AVOID

   // ADD A WALL, OR CLEAR ALL WALLS

   static public void addWall(double wall[]) { w[nWalls++] = wall; }
   static public void clearWalls() { nWalls = 0; }

   static double repulse(double x, double z, double size, int index) {
      double R = 0;

      // REPULSION FROM ALL WALLS

      for (int i = 0 ; i < nWalls ; i++) {
         int n = w[i].length/2 - 1;
         for (int j = 0 ; j < n ; j++)
            R += repulse(w[i][2*j  ]-x, w[i][2*j+1]-z,
	                 w[i][2*j+2]-x, w[i][2*j+3]-z, j==0, j==n-1, size);
      }

      // REPULSION FROM OTHER POLLYS

      for (int i = 0 ; i < nPollys ; i++)
         if (i != index)
	    R += repulse(p[i][AX]-x,p[i][AZ]-z,
	                 p[i][BX]-x,p[i][BZ]-z,true,true,.6*(size+p[i][SIZE]));

      return R;
   }

   // COMPUTE REPULSION FORCE FROM ONE WALL SECTION

   static double repulse(double ax,double az,double bx,double bz,
                         boolean aE,boolean bE, double size) {

      // COMPUTE UNIT VECTOR BETWEEN THE WALL'S TWO END POINTS

      double dx = bx - ax, dz = bz - az;
      if (dx == 0 && dz == 0) dz = .1;
      double len = Math.sqrt(dx*dx + dz*dz);
      dx /= len;
      dz /= len;

      // REPULSION DROPS OFF WITH DISTANCE AWAY FROM PLANE OF WALL

      double t = ax * dz - az * dx;
      t = Math.max(0, 2.2*size - Math.abs(t)) / (2.2*size);

      // REPULSION ALSO DROPS OFF AT THE TWO ENDS OF THE WALL

      return t*t * Math.max(0,Math.min(1, (aE?1:.5) - .5*(ax*dx + az*dz))) *
                   Math.max(0,Math.min(1, (bE?1:.5) + .5*(bx*dx + bz*dz))) ;
   }

   // STATIC DATA

   static int nWalls = 0;
   static double w[][] = new double[100][];

   final static int AX=0,AZ=1,BX=2,BZ=3,SIZE=4;

   static int nPollys = 0;
   static double p[][] = new double[100][5];
}