
import render.*;

public class HandModel extends Geometry
{
   Geometry forearm, wrist, hand, thumb, finger1, finger2, finger3, finger4;
   Geometry part;
   Matrix matrix;
   Material gold, bone;

   final static int M = 16;
   final static int N = 8;

   public final static int GOLD = 0;
   public final static int BONE = 1;

   public HandModel() {

      gold = new Material();
      gold.setAmbient (0.2, .18, .07);
      gold.setDiffuse (0.6, .54, .21);
      gold.setSpecular(1.0, .90, .35, 8);

      bone = new Material();
      bone.setAmbient(.2, .2, .2 * .8);
      bone.setDiffuse(.6, .6, .6 * .8);
      bone.setSpecular(0,0,0,1);

      add(forearm = newForearm());
      forearm.add(wrist = newWrist());
      wrist.add(hand = new Geometry());

      hand.add(thumb   = newFinger(0.8, 1.1, 0.8, 0.7,  .30, .25, .23, .18, .13));
      hand.add(finger1 = newFinger(1.6, 1.0, 0.8, 0.7,  .22, .21, .19, .17, .14));
      hand.add(finger2 = newFinger(1.6, 1.1, 0.9, 0.7,  .22, .21, .19, .17, .15));
      hand.add(finger3 = newFinger(1.5, 1.1, 0.8, 0.6,  .22, .21, .19, .17, .15));
      hand.add(finger4 = newFinger(1.3, 0.9, 0.7, 0.5,  .22, .21, .19, .17, .14));

      thumb.child(1).isVisible = false;
      thumb.child(0).child(1).isVisible = false;

      setMaterial(bone);
   }

   public void setMaterial(int m) {
      switch (m) {
      case GOLD: setMaterial(gold); break;
      case BONE: setMaterial(bone ); break;
      }
   }

   public void animate(double wx, double wy, double hf,
                double tw, double t0, double t1, double t2,
                double fw, double f0, double f1, double f2,
                double mw, double m0, double m1, double m2,
                double rw, double r0, double r1, double r2,
                double pw, double p0, double p1, double p2) {

      matrix = wrist.getMatrix();
      matrix.identity();
      matrix.rotateX(wy);
      matrix.translate(-.3 * wx, .4,0);
      matrix.rotateZ(0.70 * wx);
      matrix.translate( .3 * wx,-.4,0);

      matrix = wrist.child(0).getMatrix();
      matrix.identity();
      matrix.translate(-.2 * wx, .4,0);
      matrix.rotateZ(-0.35 * wx);
      matrix.translate( .2 * wx,-.4,0);

      matrix = thumb.getMatrix();
      matrix.identity();
      matrix.translate(-0.5,0.5,0);
      matrix.rotateZ(1.00 + tw);
      matrix.rotateX(1.3 * hf);
      matrix.rotateY(1.20);
      matrix.rotateX(0.20);

      matrix = finger1.getMatrix();
      matrix.identity();
      matrix.translate(-0.4,1.3,0);
      matrix.rotateZ(fw);
      matrix.rotateX(0.06);

      matrix = finger2.getMatrix();
      matrix.identity();
      matrix.translate( 0.05,1.4,0);
      matrix.rotateZ(-0.15 + mw);
      matrix.rotateX(0.00);

      matrix = finger3.getMatrix();
      matrix.identity();
      matrix.rotateY(-.20 * hf);
      matrix.rotateX( .06 * hf);
      matrix.translate( 0.5,1.35,0);
      matrix.rotateZ(-0.30 + rw);
      matrix.rotateX(0.03);

      matrix = finger4.getMatrix();
      matrix.identity();
      matrix.rotateY(-.4 * hf);
      matrix.rotateX( .2 * hf);
      matrix.translate( 0.95,1.2,.0);
      matrix.rotateZ(-0.45 + pw);
      matrix.rotateX(0.10);

      bendFinger(thumb  , t0, t1, t2);
      bendFinger(finger1, f0, f1, f2);
      bendFinger(finger2, m0, m1, m2);
      bendFinger(finger3, r0, r1, r2);
      bendFinger(finger4, p0, p1, p2);
   }

   void bendFinger(Geometry finger, double a, double b, double c) {
      Geometry bend0 = finger.child(0);
      Geometry bend1 = bend0.child(0).child(0);
      Geometry bend2 = bend1.child(0).child(0);
      Geometry bend3 = bend2.child(0).child(0);

      matrix = bend1.getMatrix();
      matrix.identity();
      matrix.rotateX(a);

      matrix = bend2.getMatrix();
      matrix.identity();
      matrix.rotateX(b);

      matrix = bend3.getMatrix();
      matrix.identity();
      matrix.rotateX(c);
   }


   Geometry newForearm() {
      Geometry forearm = new Geometry();

      forearm.add().globe(M,N);
      matrix = forearm.child(0).getMatrix();
      matrix.translate(-.3,0,0);
      matrix.scale(.40,.35,.35);
      matrix.rotateX(Math.PI/2);

      forearm.add().globe(M,N);
      matrix = forearm.child(1).getMatrix();
      matrix.translate(.5,-.3,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(.2,.2,.2);

      forearm.add(taperedTubeMesh(M,N,.15,.15,.34, 0.89));
      matrix = forearm.child(2).getMatrix();
      matrix.translate(-.3,0,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(1, 1, 1.15);
      matrix.translate(0, 0, 1);

      forearm.add(taperedTubeMesh(M,N,.1,.1,.2, 0.95));
      matrix = forearm.child(3).getMatrix();
      matrix.translate(.5,-.3,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(1, 1, 1);
      matrix.translate(0, 0, 1);

      forearm.add().disk(8);
      matrix = forearm.child(4).getMatrix();
      matrix.translate(-.3,0,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(.2, .2, 1.15);
      matrix.translate(0, 0, 1.89);

      forearm.add().disk(8);
      matrix = forearm.child(5).getMatrix();
      matrix.translate(.5,-.3,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(.1, .1, 1);
      matrix.translate(0, 0, 1.95);

      return forearm;
   }

   Geometry newWrist() {
      Geometry wrist = new Geometry();


      wrist.add().add().globe(M,N);
      matrix = wrist.child(0).child(0).getMatrix();
      matrix.translate(.7,.35,0);
      matrix.rotateX(Math.PI/2);
      matrix.scale(.25,.30,.25);

      return wrist;
   }

   Geometry newFinger(double a, double b, double c, double d,
                      double A, double B, double C, double D, double E) {

      Geometry finger = new Geometry();

      Geometry joint0 = finger;
      Geometry joint1 = joint0.add().add();
      Geometry joint2 = joint1.add().add();
      Geometry joint3 = joint2.add().add();
      Geometry joint4 = joint3.add().add();


      matrix = joint1.getMatrix();
      matrix.translate(0,a,0);

      matrix = joint2.getMatrix();
      matrix.translate(0,b,0);

      matrix = joint3.getMatrix();
      matrix.translate(0,c,0);

      matrix = joint4.getMatrix();
      matrix.translate(0,d,0);


      part = joint0.add().globe(M,N);
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(A,A,A);

      joint0.child(0).add(part = taperedTubeMesh(M,N, A, (A+B)/4, B, 0.95));
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(1,1,a/2);
      matrix.translate(0, 0, -1);

      part = joint1.add().globe(M,N);
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(B,B,B);

      joint1.child(0).add(part = taperedTubeMesh(M,N, B, (B+C)/4, C, 0.95));
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(1,1,b/2);
      matrix.translate(0, 0, -1);

      part = joint2.add().globe(M,N);
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(C,C,C);

      joint2.child(0).add(part = taperedTubeMesh(M,N, C, (C+D)/4, D, 0.95));
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(1,1,c/2);
      matrix.translate(0, 0, -1);

      part = joint3.add().globe(M,N);
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(D,D,D);

      joint3.child(0).add(part = taperedTubeMesh(M,N, D, (D+E)/4, E, 0.95));
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(1,1,d/2);
      matrix.translate(0, 0, -1);

      part = joint4.add().globe(M,N);
      matrix = part.getMatrix();
      matrix.rotateX(Math.PI/2);
      matrix.scale(E,E,E);


      return finger;
   }

   Geometry taperedTubeMesh(int k, int n, double r0, double r1, double r2, double len) {

      double path[][] = new double[n+1][6];
      for (int i = 0 ; i <= n ; i++) {
         double t = (double)i / n;

         path[i][0] = 0;
         path[i][1] = 0;
         path[i][2] = (2 * t - 1) * len;

         path[i][3] = lerp(4 * (t-.5) * (t-.5), r1, t < .5 ? r2 : r0);
         path[i][4] = 0;
         path[i][5] = 0;
      };
      return (new Geometry()).wire(k, path, 1);
   }

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

