// Copyright 2001 Ken Perlin

import render.*;

public class Face extends RenderApplet {

   Geometry s, eyes, rightEye, mouth, teeth, insideNose;
   Material skin;
   boolean transparentSkin = false;

   double aahTarget, oohTarget, lidsTarget, browTarget, smileTarget;

   double transition(double aTarget, double a) {
      return .5 * a + .5 * aTarget;
   }

   public void set(int i, double v) {
      switch (i) {
      case -1:
         state = 0;
         break;
      case 0:
         v = 2 * (v - .5);
         aahTarget = 1.1 * (v > 0 ? Math.pow(v,.2) : -Math.pow(-v,.2));
         state = 1;
         break;
      case 1:
         oohTarget = v - .2;
         state = 1;
         break;
      case 2:
         lidsTarget = 1.5 * v - 1.2;
         state = 1;
         break;
      case 3:
         browTarget = .1 * (v - .5);
         state = 1;
         break;
      case 4:
         smileTarget = .16 * (v - .5) + .02;
         state = 1;
         break;
      }
   }

   public void initialize() {
      setBgColor(.2,.2,.2);

      skin = new Material();
      skin.setColor(.58,.35,.15,.15,.15,.2,4).setGlow(.2,.15,.1);

      s = world.add();
      s.add().globe( 5,40, .94,1.01, 0.0,1.0);
      s.add().globe(20,20, .00,.50, 0.0,1.0);
      s.add().globe( 5,40, .49,.56, 0.0,1.0);

      s.add().globe(20,10, .55,.95, 0.0,.2);
      s.add().globe(20,20, .55,.95, 0.2,.4);
      s.add().globe(20,15, .55,.95, 0.4,.473);

      s.add().globe(10,10, .55 ,.69 , .471,.565);
      s.add().globe(25,35, .686,.814, .471,.565);
      s.add().globe(10,10, .81 ,.95 , .471,.565);

      s.add().globe(40,30, .55,.95, .564,.67);

      s.add().globe(20,20, .55,.95, .66,1.0);

      push();
         rotateX(-Math.PI/2);
         for (int i = 0 ; i < s.child.length ; i++)
            if (s.child[i] != null)
               transform(s.child[i]);
      pop();
      s.setMaterial(skin);

      Material darkRed = new Material();
      darkRed.setColor(0,0,0).setGlow(.2,0,0).setTransparency(0);

      mouth = world.add().globe(10,5, 0,1, .5,1);
      mouth.setMaterial(darkRed);

      Material white = new Material();
      white.setColor(.1,.1,.1, .5,.5,.5,30).setGlow(.6,.6,.6);

      Material iris = new Material();
      iris.setColor(.4,.5,.3).setGlow(.4,.5,.3);

      Material cornea = new Material();
      cornea.setColor(0,0,0, 10,10,10,30).setTransparency(.5);

      eyes = world.add();
      for (int i = 0 ; i < 2 ; i++) {
         eyes.add();
         eyes.child[i].add().ball(10).setMaterial(white);
         eyes.child[i].add().globe(10,3, 0,1, .9,1); // PUPIL
         eyes.child[i].add().globe(10,3, 0,1, .85,.91).setMaterial(iris);
         eyes.child[i].add().globe(10,6, 0,1, .85,1).setMaterial(cornea);
         push();
            rotateY(i==0?-.2:.2);
            translate(0,0,.02);
            transform(eyes.child[i].child[1]);
            translate(0,0,.02);
            transform(eyes.child[i].child[2]);
            scale(1,1,2);
            translate(0,0,-.44);
            transform(eyes.child[i].child[3]);
         pop();
      }

      Material dark = new Material();
      dark.setColor(0,0,0).setTransparency(.8);

      insideNose = world.add().disk(8);
      insideNose.setMaterial(dark);

      teeth = world.add().tube(10);
      teeth.setMaterial(white);

      addLight(1,1,1, 1,1,1);
      addLight(-1,-1,0, .6,.6,1);

      setFOV(.55);
   }

   private double time = 0;
   private int frameCount = 0;
   private int state = 0;

   double aah, ooh, lids, brow, smile;

   public void animate(double time) {

      skin.setTransparency(transparentSkin ? .5 : 0);

      pullCount = 0;

      this.time = time;
      boolean T = (int)time % 2 == 0;

      if (state == 0) {
         aah = Math.min(1,Math.max(-1,1.8*Noise.noise(.5*time)));
         aah = 1.1 * (aah > 0 ? Math.pow(aah, .2) : -Math.pow(-aah,.2));
         ooh = .5 + Noise.noise(time+100);
         lids = Noise.noise(time+200);
         lids = (lids < 0 ? lids : .5*Math.pow(2*lids,.2));
         lids = 1.2 * Math.pow(.5+lids,8) - .7;
         if (lids > 0) lids = .5;
         brow = .05*Noise.noise(time+300);
         smile = .2*Noise.noise(.3*time+400) + .02;
      }
      else {
         aah   = transition(aahTarget, aah);
         ooh   = transition(oohTarget, ooh);
         lids  = transition(lidsTarget, lids);
         brow  = transition(browTarget, brow);
         smile = transition(smileTarget, smile);
      }

      double x=0, xm=0, y=0, z=0, t=0, l=(.5-lids)/1.3;

      double theta = .02 * Noise.noise(time);
      double phi   = .02 * Noise.noise(time + 100);


      push();
         if (frameCount < 2)
            scale(0,0,0);

         rotateY(theta);
         rotateX(phi);
         push();
            translate(0,.79,.92);
            double lookX = .007*((int)(1000*Noise.noise(.2*time + 1000))%10);
            double lookY = .01 + .05*Noise.noise(5*time + 1100);
            push();
               translate(-.59,0,0);
               rotateY(lookX);
               rotateX(lookY);
               scale(.3,.3,.3);
               transform(eyes.child[0]);
            pop();
            push();
               translate(.59,0,0);
               rotateY(lookX);
               rotateX(lookY);
               scale(.3,.3,.3);
               transform(eyes.child[1]);
            pop();
         pop();

         push();
            translate(0,-.19,1.45 + .04*ooh);
            scale(.34-.25*ooh,.2,.1);
            transform(mouth);
         pop();

         push();
            translate(0,-.03+.01*aah,1.25);
            rotateX(-Math.PI/2);
            scale(.36,.4,.1);
            transform(teeth);
         pop();

         push();
            translate(0,.3,1.73);
            rotateX(Math.PI/4);
            scale(.2,.2,1);
            transform(insideNose);
         pop();
      pop();

      push();
	 if (frameCount < 1)
	    scale(0,0,0);

         scale(2.05,3,2.2);
         transform(s);
         push(); // CRANIAL BULGE IN BACK
            scale(1,1,1.1);
            pull(s, 0,0,0, -.4,.4,1, 0,-1,-1);
         pop();
         push(); // LATERAL CRANIAL BULGE
            scale(1.1,1,1);
            pull(s, 0,0,0, .5,.85,1, 0,0,0);
         pop();
         push(); // ADJUST SHAPE OF TOP OF HEAD
            scale(1,.975,1);
            translate(0,0,-.05);
            pull(s, -.9,0,.9, .5,1,1, 0,0,0);
         pop();
         push(); // NECK
            scale(.4,1,.35);
            pull(s, 0,0,0, 1,-.7,-.7, 0,0,0);
            scale(5,1,7);
            pull(s, 0,0,0, -.4,-1,-1, 0,0,0);
         pop();
         push(); // TEMPLES
            scale(.8,.9,1);
            pull(s, 0,1,1,   1,1,-.2, 0,0,0);
            pull(s, 0,-1,-1, 1,1,-.2, 0,0,0);
         pop();
         push(); // JAW
            rotateX(.21+.04*aah);
            scale(1.18,1,1.3);
            translate(0,.03,.13);
            pull(s, -.9,0,.9, .3,-.2,-.4, .0,.2,.2);
         pop();

         push(); // CHIN
            translate(0,-.03-.02*aah,.05-.03*aah);
            pull(s, -.4,0,.4, -.38,-.26,-.2-.03*aah, 0,1,1);
         pop();
         push(); // EYE SOCKETS
            push();
               translate(0,0,-.2);
               pull(s, -.01,.2,.2,  .45,.3,.1+.5*smile, 0,1,1);
               pull(s, .01,-.2,-.2, .45,.3,.1+.5*smile, 0,1,1);
            pop();
            push();
               translate(0,brow>0?1.2*brow:2*brow,-.03);
               pull(s, -.45,0,.45, .2,.45,.6, 0,1,1);
            pop();
            push();
               translate(0,0,-.03);
               pull(s, 0,0,0, .3,.1,.1, 0,1,1);
            pop();
         pop();
         push(); // EYE LIDS
            y = -.015 + .1*lids;
            z = 0;
            push(); // TO HELP EYES TO SHUT
               translate(0,0,.2 * (.5+.5*lids));
               pull(s, -.10,-.36,-.53,  .29,.27,.25, .2,.8,.8);
               pull(s,  .10, .36, .53,  .29,.27,.25, .2,.8,.8);
            pop();

            translate(0,0,.2);
            push(); // UPPER LID
               translate(0,-.37,-z);
               rotateX(y);
               translate(0,.37,z+.01);
               pull(s,  .10, .36, .53,  .39,.285,.27, .2,.8,.8);
               pull(s, -.10,-.36,-.53,  .39,.285,.27, .2,.8,.8);
            pop();

            push(); // LOWER LID
               t = Math.abs(smile) * l * .5;
               x = smile > 0 ? 0 : .4*t;
               translate(0,-.37,-z);
               rotateX(-y);
               translate(0,.37+t,z+y+.7*x);
               pull(s,  .10-x/2, .36-x, .53,  .27,.255,.17, .2,.9,.9);
               pull(s, -.10+x/2,-.36+x,-.53,  .27,.255,.17, .2,.9,.9);
            pop();
         pop();
         push(); // CHEEKS
            push();
               translate(.5*smile,-.04+.03*lids+.5*smile,.25);
               pull(s, .1,.7,.7,   .32,.12,-.2, 0,1,1);
            pop();
            push();
               translate(-.5*smile,-.04+.03*lids+.5*smile,.25);
               pull(s, -.1,-.7,-.7, .32,.12,-.2, 0,1,1);
            pop();
         pop();
         push(); // BROW FURROWS
            translate(0,0,-.05);
            pull(s, -.6,.4,.4,  .4+brow,.6,.6, 0,1,1);
            pull(s, .6,-.4,-.4, .4+brow,.6,.6, 0,1,1);
         pop();
         push(); // NOSE
            push();
               translate(0,-.01,.20);
               pull(s, -.25,0,.25, .36,.15,.015, 0,1,1);
               translate(0,.037 + .003*aah,-.18);
               pull(s, -.15,0,.15, .24,.15,.033, 0,1,1);
            pop();
            push();
               translate(0,.02+.006*aah,0); // NOSTRILS
               push();
                  push();
                     translate(-.04,0,0);
                     pull(s, -.2,-.1,0, .2,.09,.06, .73,.81,.88);
                  pop();
                  translate(0,.2,-.15);
                  pull(s, -.085,-.045,-.01, .12,.06,.05, 0,1,1);
               pop();
               push();
                  push();
                     translate(.04,0,0);
                     pull(s, .2,.1,0, .2,.09,.06, .73,.81,.88);
                  pop();
                  translate(0,.2,-.15);
                  pull(s,  .085, .045, .01, .12,.06,.05, 0,1,1);
               pop();
            pop();
         pop();
         push(); // LIPS
            x = .35-.2*ooh;
            push(); // MOUTH CAVITY
               translate(0,0,-.01-.3*Math.pow(Math.max(.01,.3+.7*aah), .2));
               pull(s, -x,0,x, -.035,-.055,-.08, 0,1,1);
            pop();
            push(); // UPPER LIP
               xm = Math.max(x,.3);
               translate(0,-.003+.012*aah,.065+.01*(ooh-aah));
               translate(0,.13,.63);
               rotateX(-.65); // CURL UP
               translate(0,-.13,-.63);
               pull(s, -x,0,x, -.08,-.05,.18, 0,1,1);
            pop();
            push(); // LOWER LIP
               y = .03*aah;
               translate(0,-y,.03+.005*(ooh-aah));
               pull(s, -x,0,x, -.20-.03*ooh,-.09,-.06, 0,1,1);
               translate(0,-.16,.75);
               rotateX(.3); // CURL DOWN
               translate(0,.16,-.75);
               pull(s, -x,0,x, -.21,-.12,-.06, .7,.75,.75);
            pop();
            translate(0,0,.04*ooh); // PUSH MOUTH FWD WHEN PUCKERING
            pull(s, -x-.1,0,x+.1, -.20,-.05,.01, 0,1,1);
            push(); // PULL UP OR DOWN SIDES OF FACE AROUND MOUTH
               x = .45-.2*ooh;
               translate(0,-.02-.02*aah + smile,0);
               pull(s, 0, x, 2*x, -.8,-.02,.2, 0,1,1);
               pull(s, 0,-x,-2*x, -.8,-.02,.2, 0,1,1);
            pop();
         pop();
         push(); // TURN HEAD
            rotateY(theta);
            rotateX(phi);
            pull(s, 0,0,0, -1,-.2,-.2, 0,0,0);
         pop();
      pop();

      frameCount++;
   }

   public void setTransparent() {
      transparentSkin = !transparentSkin;
      renderer.updateTransparency = true;
   }

   public void setVisibleMesh() {
      if (transparentSkin)
         setTransparent();
      renderer.showMesh = ! renderer.showMesh;
   }

   private int pullCount = 0;
   private double pStartTime;
   private boolean progression = false;

   public void setProgression() {
      progression = true;
      pStartTime = time;
      maskTime = time;
      for (int i = 0 ; i < mask.length ; i++)
         mask[i] = 0xffffffff;
      pullCount = 0;
   }

   private final int nPulls = 37;
   private int mask[] = new int[nPulls];
   private double maskTime = 0;

   public int pull(Geometry s, double x0,double x1,double x2,
                      double y0,double y1,double y2,
                      double z0,double z1,double z2) {
      Geometry.pullWeight = 1;
      Geometry.pullMask = 0xffffffff;
      if (progression) {
         double t = time - pStartTime;
         if (t >= nPulls)
            progression = false;
         else {
            t = t % nPulls;
            if (t < pullCount) 
               return 0;
            if (t < pullCount+1)
               Geometry.pullWeight = t - pullCount;
         }
      }

      if (time - maskTime > 0 && mask[pullCount] != 0)
         Geometry.pullMask = mask[pullCount];
      int msk = super.pull(s, x0,x1,x2, y0,y1,y2, z0,z1,z2);
      if (time - maskTime > 1)
         mask[pullCount] |= msk;
      pullCount++;
      return 0;
   }
}