//

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,0.0, 0.2,0.0,1.0, 0.0,1.0,1.1, 1.0,0.8,0.0, 0.8,0.0,0.1, 1.0,1.0,0.9,
         0.1,1.0,0.1, 0.0,0.3,0.2, 0.1,0.8,1.1, 1.1,1.0,0.1, 0.8,0.0,.55, 1.1,0.8,1.1,
         0.0,0.8,0.0, 0.2,0.0,0.1, 0.0,1.0,0.9, 1.0,0.6,0.0, 0.8,0.0,1.0, 1.0,1.0,1.1,
         -.1,1.0,0.1, 0.2,0.0,.55, -.1,0.8,1.1, 0.9,1.0,0.1, 1.0,0.3,0.2, 0.9,0.8,1.1, };
      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);
   }

   Geometry eyes = null;

   void addEyes() {
      eyes = body.add();

      Material white = (new Material()).setAmbient(1,1,1);
      eyes.add().ball(3).setMaterial(white);
      eyes.add().ball(3).setMaterial(white);

      Material black = (new Material()).setAmbient(0,0,0).setDiffuse(0,0,0);
      eyes.add().ball(3).setMaterial(black);
      eyes.add().ball(3).setMaterial(black);
   }

   double ab[] = new double[3], e[] = new double[3], blinkStartTime = 0, v[] = new double[3];

   public static final int EYES_APART  = 0;
   public static final int EYES_DILATE = 1;
   public static final int EYES_HEIGHT = 2;
   public static final int EYES_TILT   = 3;
   public static final int EYES_WIDTH  = 4;

   public void set(int type, double value) {
      value = Math.max(0, Math.min(1, value));
      switch (type) {
      case EYES_APART:  eyes_apart  = value; break;
      case EYES_DILATE: eyes_dilate = value; break;
      case EYES_HEIGHT: eyes_height = value; break;
      case EYES_TILT:   eyes_tilt   = value; break;
      case EYES_WIDTH:  eyes_width  = value; break;
      }
   }

   double eyes_apart  = .5;
   double eyes_height = .5;
   double eyes_dilate = .5;
   double eyes_tilt   = .5;
   double eyes_width  = .5;

   void animateEyes(double time) {

      // BLINKING

      eyes.getMatrix().identity();
      if (time - blinkStartTime > .3 && time % 2 < 0.7 && time % 2.3 < 0.9)
	 blinkStartTime = time;
      if (time - blinkStartTime < .1)
	 eyes.getMatrix().scale(0,0,0);

      // CONVERT PARAMETERS TO ACTUAL VALUES

      double tilt    = -.78 + 1.56 * eyes_tilt;
      double apart   =  .10 +  .10 * eyes_apart;
      double height  =  .05 +  .15 * eyes_height;
      double width   =  .05 +  .15 * eyes_width;
      double dilate  =  .03 +  .09 * eyes_dilate;

      // LOCATE THE CENTER POINT BETWEEN THE EYES

      double a[] = body.vertices[7], b[] = body.vertices[8];
      double head_tilt = b[1] - a[1];
      for (int i = 0 ; i < 3 ; i++)
         ab[i] = L(.5, a[i], b[i]);

      a = body.vertices[6]; b = body.vertices[9];
      for (int i = 0 ; i < 3 ; i++)
         v[i] = L(.5, a[i], b[i]) - ab[i];
      double h = Vec.norm(v);

      Matrix m0 = eyes.child(0).getMatrix();
      Matrix m1 = eyes.child(1).getMatrix();
      Matrix m2 = eyes.child(2).getMatrix();
      Matrix m3 = eyes.child(3).getMatrix();

      // LEFT EYE

      e[0] = -apart;
      e[1] = .2 - .5 * h;
      e[2] = .03 - .15 * Math.max(0,b[2]-.5);
      Vec.rotate(e, 2, head_tilt);

      m0.identity();
      m0.translate(ab[0] + e[0], ab[1] + e[1], ab[2] + e[2]);
      m0.rotateX(-.5);
      m0.rotateZ(head_tilt + tilt);
      m2.copy(m0);
      m0.scale(width,height,.05);
      m2.translate(0,.02,-.05);
      m2.scale(dilate,dilate,.04);

      // RIGHT EYE

      e[0] = apart;
      e[1] = .2 - .5 * h;
      e[2] = .03 - .15 * Math.max(0,a[2]-.5);
      Vec.rotate(e, 2, head_tilt);

      m1.identity();
      m1.translate(ab[0] + e[0], ab[1] + e[1], ab[2] + e[2]);
      m1.rotateX(-.5);
      m1.rotateZ(head_tilt - tilt);
      m3.copy(m1);
      m1.scale(width,height,.05);
      m3.translate(0,.02,-.05);
      m3.scale(dilate,dilate,.04);
   }

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

   // 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 setActions(String actions) {
      for (int i = 0 ; i < actions.length() ; i++)
	 setAction(actions.charAt(i) - 'a');
   }

   public void setAction(int action) {

      // PREPARE TO SET AN EXPRESSION

      if (action == 'x' - 'a') {
	 settingExpression = true;
	 return;
      }

      // CHOOSE TYPE OF EXPRESSION

      if (settingExpression) {
	 settingExpression = false;
         switch (action + 'a') {
         case 'a': expressionType = Polly.EYES_APART ; break;
         case 'd': expressionType = Polly.EYES_DILATE; break;
         case 'h': expressionType = Polly.EYES_HEIGHT; break;
         case 't': expressionType = Polly.EYES_TILT  ; break;
         case 'w': expressionType = Polly.EYES_WIDTH ; break;
         }
	 return;
      }

      // SET EXPRESSION

      if (expressionType >= 0) {
	 int digit = action + 'a';
         if (digit >= '1' && digit <= '9')
	    set(expressionType, (digit - '1') / 8.);
	 expressionType = -1;
	 return;
      }

      if (action >= 0 && action < nActions && action != action1) {
         action0 = action1;
         action1 = action;
         transition = 0;
      }
   }

   boolean settingExpression = false;
   int expressionType = -1;

   // 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();

      if (eyes != null)
	 animateEyes(time);
   }

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